├── src ├── main │ ├── resources │ │ ├── application.yml │ │ └── META-INF │ │ │ └── spring.factories │ ├── .DS_Store │ └── java │ │ └── com │ │ └── ioevent │ │ └── starter │ │ ├── enums │ │ ├── MessageTypesEnum.java │ │ └── EventTypesEnum.java │ │ ├── annotations │ │ ├── IOMessage.java │ │ ├── ConditionalIOResponse.java │ │ ├── EventBody.java │ │ ├── EventKey.java │ │ ├── IOTimer.java │ │ ├── IOHeaders.java │ │ ├── IOHeader.java │ │ ├── IOPayload.java │ │ ├── EndEvent.java │ │ ├── EnableIOEvent.java │ │ ├── StartEvent.java │ │ ├── InputEvent.java │ │ ├── IOResponse.java │ │ ├── OutputEvent.java │ │ ├── ExceptionEvent.java │ │ ├── IOFlow.java │ │ ├── GatewayInputEvent.java │ │ ├── GatewayOutputEvent.java │ │ └── IOEvent.java │ │ ├── domain │ │ ├── RegistryAction.java │ │ ├── IOEventHeaders.java │ │ ├── IOEventType.java │ │ ├── ParallelEventInfo.java │ │ ├── IOTimerEvent.java │ │ ├── IOEventExceptionInformation.java │ │ ├── IOEventMessageEventInformation.java │ │ ├── IOEventGatwayInformation.java │ │ └── IOEventParallelEventInformation.java │ │ ├── handler │ │ ├── IOEventRecordInfoService.java │ │ └── IOEventRecordInfo.java │ │ ├── configuration │ │ ├── postprocessor │ │ │ ├── IOEventPostProcessors.java │ │ │ └── BeanMethodPair.java │ │ ├── swagger │ │ │ └── SwaggerConfiguration.java │ │ ├── context │ │ │ └── AppContext.java │ │ ├── properties │ │ │ └── IOEventProperties.java │ │ ├── kafka │ │ │ └── KafkaConfig.java │ │ ├── IOEventConfiguration.java │ │ └── aspect │ │ │ └── v2 │ │ │ └── IOEventEndAspect.java │ │ ├── service │ │ ├── IOEventContextHolder.java │ │ ├── TopicServices.java │ │ └── IOEventRegistryService.java │ │ ├── IOEventStarterApplication.java │ │ ├── controller │ │ └── IOEventController.java │ │ ├── stream │ │ ├── TimerStream.java │ │ ├── ParallelStream.java │ │ └── MessageStream.java │ │ ├── listener │ │ ├── ListenerCreator.java │ │ ├── IOEventTimerListener.java │ │ ├── Listener.java │ │ ├── MessageListener.java │ │ └── IOEventParrallelListener.java │ │ └── logger │ │ └── EventLogger.java ├── .DS_Store └── test │ └── java │ └── com │ └── ioevent │ └── starter │ ├── listener │ ├── MessageListenerTest.java │ └── IOEventParrallelListenerTest.java │ ├── service │ ├── IOEventContextHolderTest.java │ └── TopicServicesTest.java │ ├── configuration │ ├── postprocessor │ │ ├── IOEventTopicBeanPostProcessorTest.java │ │ └── IOEventBpmnPostProcessorTest.java │ └── aspect │ │ └── v2 │ │ ├── IOEventStartAspectTest.java │ │ └── IOEventEndAspectTest.java │ └── handler │ └── RecordsHandlerTest.java ├── .DS_Store ├── .gitignore ├── Jenkinsfile ├── docker-compose.yml ├── README.md └── pom.xml /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioevent-io/ioevent-spring-starter/HEAD/.DS_Store -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioevent-io/ioevent-spring-starter/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /src/main/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioevent-io/ioevent-spring-starter/HEAD/src/main/.DS_Store -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/enums/MessageTypesEnum.java: -------------------------------------------------------------------------------- 1 | package com.ioevent.starter.enums; 2 | 3 | public enum MessageTypesEnum { 4 | THROW,CATCH; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/enums/EventTypesEnum.java: -------------------------------------------------------------------------------- 1 | package com.ioevent.starter.enums; 2 | 3 | public enum EventTypesEnum { 4 | SERVICE,RECEIVE,USER,MANUAL,SEND,SCRIPT,BUSINESS_RULE,START_CONDITIONAL_EVENT; 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | #Eclipse 23 | .* 24 | bin/ 25 | 26 | ### NetBeans ### 27 | /nbproject/private/ 28 | /nbbuild/ 29 | /dist/ 30 | /nbdist/ 31 | /.nb-gradle/ 32 | build/ 33 | !**/src/main/**/build/ 34 | !**/src/test/**/build/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/IOMessage.java: -------------------------------------------------------------------------------- 1 | package com.ioevent.starter.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import com.ioevent.starter.enums.MessageTypesEnum; 9 | 10 | @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface IOMessage { 13 | 14 | String key() default ""; 15 | 16 | MessageTypesEnum messageType() default MessageTypesEnum.THROW; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/ConditionalIOResponse.java: -------------------------------------------------------------------------------- 1 | package com.ioevent.starter.annotations; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ConditionalIOResponse extends IOResponse { 7 | 8 | /** 9 | * attribute for conditional start event 10 | */ 11 | private boolean condition = true; 12 | /** 13 | * create IOResponse with conditional Used mostly in conditional start event 14 | * 15 | * @param key 16 | * @param body 17 | * @param conditional 18 | */ 19 | public ConditionalIOResponse(String key, T body, boolean conditional) { 20 | super(key,body); 21 | this.condition = conditional; 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/domain/RegistryAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.domain; 21 | 22 | 23 | 24 | 25 | 26 | 27 | public enum RegistryAction { 28 | CLOSE, REGISTER 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/handler/IOEventRecordInfoService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.handler; 21 | 22 | 23 | 24 | 25 | 26 | 27 | public class IOEventRecordInfoService { 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/configuration/postprocessor/IOEventPostProcessors.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.configuration.postprocessor; 21 | 22 | 23 | 24 | 25 | 26 | 27 | public interface IOEventPostProcessors { 28 | 29 | void process(Object bean, String workFlow) throws Exception, Throwable; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/EventBody.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.annotations; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import java.lang.annotation.ElementType; 28 | import java.lang.annotation.Retention; 29 | import java.lang.annotation.RetentionPolicy; 30 | import java.lang.annotation.Target; 31 | 32 | @Target({ElementType.PARAMETER}) 33 | @Retention(RetentionPolicy.RUNTIME) 34 | public @interface EventBody { 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/EventKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.annotations; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import java.lang.annotation.ElementType; 28 | import java.lang.annotation.Retention; 29 | import java.lang.annotation.RetentionPolicy; 30 | import java.lang.annotation.Target; 31 | 32 | @Target({ElementType.PARAMETER}) 33 | @Retention(RetentionPolicy.RUNTIME) 34 | public @interface EventKey { 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/domain/IOEventHeaders.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.domain; 21 | 22 | 23 | 24 | 25 | 26 | /** 27 | * enum which describe IOEvent custom Headers for events, 28 | **/ 29 | public enum IOEventHeaders { 30 | CORRELATION_ID,STEP_NAME,INPUT,EVENT_TYPE,OUTPUT_EVENT,START_TIME,API_KEY,PROCESS_NAME,START_INSTANCE_TIME,IMPLICIT_START,IMPLICIT_END, 31 | ERROR_TYPE,ERROR_MESSAGE,ERROR_TRACE,RESUME,MESSAGE_KEY,APPLICATION_PREFIX 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/domain/IOEventType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.domain; 21 | 22 | 23 | 24 | 25 | 26 | /** 27 | * enum which describe Event (task) type, 28 | **/ 29 | public enum IOEventType { 30 | START, TASK, GATEWAY_PARALLEL, GATEWAY_EXCLUSIVE, END, IMPLICITTASK, ERROR_BOUNDRY, ERROR_END, UNHANDLED_ERROR, START_TIMER, INTERMEDIATE_TIMER, BOUNDRY_TIMER,START_CONDITIONAL, MESSAGE_THROW, MESSAGE_CATCH; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/IOTimer.java: -------------------------------------------------------------------------------- 1 | package com.ioevent.starter.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * IOTimer annotation to schedule timed events for specific period or date 11 | **/ 12 | @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface IOTimer { 15 | /** 16 | * The cron expression is the string to define specific period or date. 17 | * 18 | * @return cron expression string 19 | */ 20 | String cron() default ""; 21 | 22 | /** 23 | * limit to interrupt. 24 | * 25 | * @return 26 | */ 27 | long limit() default 0; 28 | 29 | /** 30 | * intermediate delay. 31 | * 32 | * @return 33 | */ 34 | long delay() default 0; 35 | 36 | /** 37 | * Time unit used for the timer duration 38 | * @return 39 | */ 40 | TimeUnit timeUnit() default TimeUnit.SECONDS; 41 | 42 | } -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/IOHeaders.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.annotations; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * IOHeaders annotation indicates that a method parameter should be bound to 26 | * the headers of the event. The annotated parameter must be 27 | * assignable to Map with String keys and Object values. 28 | * 29 | */ 30 | @Target(ElementType.PARAMETER) 31 | @Retention(RetentionPolicy.RUNTIME) 32 | public @interface IOHeaders { 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/configuration/swagger/SwaggerConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.configuration.swagger; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import org.springdoc.core.GroupedOpenApi; 28 | import org.springframework.context.annotation.Bean; 29 | import org.springframework.context.annotation.Configuration; 30 | 31 | 32 | @Configuration 33 | public class SwaggerConfiguration { 34 | 35 | @Bean 36 | public GroupedOpenApi publicApi() { 37 | return GroupedOpenApi.builder() 38 | .group("ioevent") 39 | .pathsToMatch("/public/**") 40 | .build(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/IOHeader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.ioevent.starter.annotations; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import java.lang.annotation.Target; 22 | 23 | /** 24 | * IOHeader annotation indicates that a method parameter should be bound to the 25 | * header of the event with the same name as the annotaion value . 26 | * 27 | */ 28 | @Target(ElementType.PARAMETER) 29 | @Retention(RetentionPolicy.RUNTIME) 30 | public @interface IOHeader { 31 | /** 32 | * The name of the request header to bind to. 33 | * 34 | * @return name of the header desired to get 35 | */ 36 | String value() default ""; 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/service/IOEventContextHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.service; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import com.ioevent.starter.handler.IOEventRecordInfo; 28 | 29 | public class IOEventContextHolder { 30 | 31 | private static ThreadLocal eventContextHolder = new ThreadLocal<>(); 32 | 33 | public static void setContext(IOEventRecordInfo ioeventRecordInfo) { 34 | eventContextHolder.set(ioeventRecordInfo); 35 | } 36 | 37 | public static IOEventRecordInfo getContext() { 38 | return eventContextHolder.get(); 39 | } 40 | 41 | public static void unload() { 42 | eventContextHolder.remove(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/IOEventStarterApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 28 | 29 | import com.ioevent.starter.configuration.properties.IOEventProperties; 30 | 31 | import lombok.extern.slf4j.Slf4j; 32 | 33 | 34 | /** 35 | * IOEvent Starter Main Class 36 | **/ 37 | @Slf4j 38 | @EnableConfigurationProperties(IOEventProperties.class) 39 | //@Profile("development") 40 | //@Configuration 41 | //@EnableDiscoveryClient 42 | public class IOEventStarterApplication { 43 | // public static void main(String[] args) { 44 | // 45 | // SpringApplication.run(IOEventStarterApplication.class, args); 46 | // } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/ioevent/starter/listener/MessageListenerTest.java: -------------------------------------------------------------------------------- 1 | package com.ioevent.starter.listener; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import org.junit.Assert; 7 | import org.junit.jupiter.api.Test; 8 | import org.mockito.InjectMocks; 9 | 10 | import com.ioevent.starter.domain.IOEventMessageEventInformation; 11 | 12 | public class MessageListenerTest { 13 | 14 | 15 | @InjectMocks 16 | MessageListener messageListener = new MessageListener(); 17 | 18 | 19 | @Test 20 | void isValidMessageTest() 21 | { 22 | IOEventMessageEventInformation iOEventMessageEventInformation = new IOEventMessageEventInformation(); 23 | List required = Arrays.asList("exemple1","exemple2","exemple3"); 24 | List arrived = Arrays.asList("exemple2","exemple3","exemple1"); 25 | String messageArrived = "messageExample"; 26 | String messageRequired = "messageExample"; 27 | String messageNonRequired = "messageNonExample"; 28 | iOEventMessageEventInformation.setInputsArrived(arrived); 29 | iOEventMessageEventInformation.setInputRequired(required); 30 | iOEventMessageEventInformation.setMessageEventRequired(messageRequired); 31 | iOEventMessageEventInformation.setMessageEventArrived(messageNonRequired); 32 | Assert.assertFalse(messageListener.validMessage(iOEventMessageEventInformation)); 33 | iOEventMessageEventInformation.setMessageEventArrived(messageArrived); 34 | Assert.assertTrue(messageListener.validMessage(iOEventMessageEventInformation)); 35 | 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/IOPayload.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.annotations; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * IOPayload annotation binds a method parameter to the payload of a the 26 | * received event. It can be used also in the parallel input gateway to bind 27 | * between method parameters and inputs payloads using input index. 28 | * 29 | */ 30 | @Target(ElementType.PARAMETER) 31 | @Retention(RetentionPolicy.RUNTIME) 32 | public @interface IOPayload { 33 | 34 | /** 35 | * The index of the request payload to bind from the multiple inputs payloads . 36 | * 37 | * @return index of payload 38 | */ 39 | int index() default 0; 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/EndEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.annotations; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * EndEvent annotation define the finishing point of a process which includes a 26 | * key where we specify the name of the flow. 27 | * 28 | * , 29 | **/ 30 | @Target({ ElementType.METHOD }) 31 | @Retention(RetentionPolicy.RUNTIME) 32 | public @interface EndEvent { 33 | /** 34 | * The name of the IOFlow , which this end event belong to 35 | * 36 | * @return the IOFlow name 37 | */ 38 | String value() default ""; 39 | 40 | /** 41 | * The name of the IOFlow , which this end event belong to 42 | * 43 | * @return the IOFlow name 44 | */ 45 | String key() default ""; 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | pipeline { 4 | 5 | agent any 6 | 7 | tools { 8 | jdk 'jdk11' 9 | maven 'M3' 10 | } 11 | 12 | triggers { pollSCM('*/5 * * * *') } 13 | 14 | options { 15 | buildDiscarder(logRotator(numToKeepStr: '5')) 16 | } 17 | 18 | stages { 19 | 20 | stage('Build Stage') { 21 | steps { 22 | sh "mvn clean install" 23 | } 24 | } 25 | 26 | stage('SonarQube analysis') { 27 | steps { 28 | withSonarQubeEnv('AWS SONAR') { 29 | sh 'mvn org.sonarsource.scanner.maven:sonar-maven-plugin:3.9.1.2184:sonar' 30 | } 31 | } 32 | } 33 | 34 | stage('Push grizzly starter dependencies to community maven repository') { 35 | when { branch "develop" } 36 | steps { 37 | sh "mvn clean deploy" 38 | } 39 | } 40 | 41 | } 42 | post { 43 | always { 44 | junit '**/surefire-reports/*.xml' 45 | } 46 | 47 | failure { 48 | emailext ( 49 | subject: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'", 50 | body: """

FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]':

51 |

Check console output at "${env.JOB_NAME} [${env.BUILD_NUMBER}]"

""", 52 | recipientProviders: [[$class: 'DevelopersRecipientProvider'],[$class: 'CulpritsRecipientProvider']] 53 | ) 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/EnableIOEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.annotations; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | import org.springframework.context.annotation.Import; 25 | 26 | import com.ioevent.starter.configuration.IOEventConfiguration; 27 | 28 | /** 29 | * EnableIOEvent annotation allows us to enable the configuration class from the 30 | * starter in any application that use our starter, a single @EnableIOEvent 31 | * annotation used on the "application class" will enable IOEventConfiguration 32 | * by loading all the beans of IOEventConfiguration class 33 | **/ 34 | @Target(ElementType.TYPE) 35 | @Retention(RetentionPolicy.RUNTIME) 36 | @Import({ IOEventConfiguration.class }) 37 | public @interface EnableIOEvent { 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/StartEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.annotations; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * StartEvent annotation define the starting point of a process which includes a 26 | * key where we specify the name of the IOFlow. 27 | **/ 28 | @Target({ ElementType.METHOD }) 29 | @Retention(RetentionPolicy.RUNTIME) 30 | public @interface StartEvent { 31 | /** 32 | * The name of the IOFlow , which this start event belong to 33 | * 34 | * @return the IOFlow name 35 | */ 36 | String value() default ""; 37 | 38 | /** 39 | * The name of the IOFlow , which this start event belong to 40 | * 41 | * @return the IOFlow name 42 | */ 43 | String key() default ""; 44 | 45 | /** 46 | * StartTimer to schedule start events for specific period or date. 47 | * 48 | * @return IOTimer Object 49 | */ 50 | IOTimer timer() default @IOTimer(); 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/configuration/context/AppContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.configuration.context; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import org.springframework.beans.BeansException; 28 | import org.springframework.context.ApplicationContext; 29 | import org.springframework.context.ApplicationContextAware; 30 | 31 | /** 32 | * class to get the current application context 33 | **/ 34 | public class AppContext implements ApplicationContextAware { 35 | private static ApplicationContext ctx = null; 36 | 37 | /** 38 | * method to get the context 39 | * 40 | * @return ctx for the ApplicationContext, 41 | */ 42 | public static ApplicationContext getApplicationContext() { 43 | return ctx; 44 | } 45 | 46 | /** 47 | * method for setting the context 48 | * 49 | * @param ctx for the context, 50 | * @throws BeansException type of Exception, 51 | */ 52 | @Override 53 | public void setApplicationContext(ApplicationContext ctx) throws BeansException { 54 | this.ctx = ctx; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/domain/ParallelEventInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.domain; 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | import java.util.List; 29 | 30 | /** 31 | * class which define the parallel event model information : 32 | * - id for the ID of the task, 33 | * - outputs for the list of outputs of the task, 34 | */ 35 | public class ParallelEventInfo { 36 | private String id; 37 | private List outputs; 38 | 39 | public ParallelEventInfo() { 40 | super(); 41 | } 42 | 43 | public ParallelEventInfo(String id, List outputs) { 44 | this.id = id; 45 | this.outputs = outputs; 46 | } 47 | 48 | public String getId() { 49 | return id; 50 | } 51 | 52 | public void setId(String id) { 53 | this.id = id; 54 | } 55 | 56 | public List getOutputs() { 57 | return outputs; 58 | } 59 | 60 | public void setOutputs(List outputs) { 61 | this.outputs = outputs; 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return "CustomEvent [id=" + id + ", outputs=" + outputs + "]"; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/com/ioevent/starter/service/IOEventContextHolderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.service; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import static org.junit.jupiter.api.Assertions.assertNull; 28 | 29 | import org.junit.Assert; 30 | import org.junit.jupiter.api.Test; 31 | import org.springframework.util.StopWatch; 32 | 33 | import com.ioevent.starter.handler.IOEventRecordInfo; 34 | 35 | class IOEventContextHolderTest { 36 | 37 | @Test 38 | void ioeventContextTest() { 39 | IOEventRecordInfo ioeventRecordInfo = new IOEventRecordInfo("object stored in thread local", "name", "output", new StopWatch(),1000L,null); 40 | IOEventContextHolder.setContext(ioeventRecordInfo); 41 | Assert.assertEquals(ioeventRecordInfo, IOEventContextHolder.getContext()); 42 | } 43 | @Test 44 | void ioeventContextUnloadTest() { 45 | IOEventRecordInfo ioeventRecordInfo = new IOEventRecordInfo("object stored in thread local", "name", "output", new StopWatch(),1000L,null); 46 | IOEventContextHolder.setContext(ioeventRecordInfo); 47 | Assert.assertEquals(ioeventRecordInfo, IOEventContextHolder.getContext()); 48 | IOEventContextHolder.unload(); 49 | assertNull(IOEventContextHolder.getContext());} 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/InputEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.annotations; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * InputEvent create a Listener which receive events from the topic ( if the 26 | * topic is not mentioned it will listen to the generic topic specified in 27 | * the @IOEvent or @IFlow annotation ), and while the listener consumes an event 28 | * it will verify if the output key of the received event is equal to 29 | * the @InputEvent key in order to invoke the specific method. 30 | **/ 31 | @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) 32 | @Retention(RetentionPolicy.RUNTIME) 33 | public @interface InputEvent { 34 | /** 35 | * Specify the name of the input event 36 | * 37 | * @return the input event name 38 | */ 39 | String value() default ""; 40 | 41 | /** 42 | * The key of the input , which considered as input event name 43 | * 44 | * @return the input event name 45 | */ 46 | String key() default ""; 47 | 48 | /** 49 | * The topic name from where to consume events that can invoke the method 50 | * 51 | * @return the topic name 52 | */ 53 | String topic() default ""; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/domain/IOTimerEvent.java: -------------------------------------------------------------------------------- 1 | package com.ioevent.starter.domain; 2 | 3 | public class IOTimerEvent { 4 | 5 | String cron; 6 | String methodName; 7 | String methodeQialifiedName; 8 | String bean; 9 | String appName; 10 | Long time ; 11 | 12 | public IOTimerEvent() { 13 | super(); 14 | } 15 | 16 | 17 | public IOTimerEvent(String cron, String methodName,String methodeQialifiedName, String beanName,String appName,Long time) { 18 | super(); 19 | this.cron = cron; 20 | this.methodName = methodName; 21 | this.methodeQialifiedName=methodeQialifiedName; 22 | this.bean = beanName; 23 | this.appName=appName; 24 | this.time=time; 25 | } 26 | 27 | public String getCron() { 28 | return cron; 29 | } 30 | 31 | public void setCron(String cron) { 32 | this.cron = cron; 33 | } 34 | 35 | public String getMethodName() { 36 | return methodName; 37 | } 38 | 39 | 40 | public void setMethodName(String methodName) { 41 | this.methodName = methodName; 42 | } 43 | 44 | 45 | public String getMethodeQialifiedName() { 46 | return methodeQialifiedName; 47 | } 48 | 49 | 50 | public void setMethodeQialifiedName(String methodeQialifiedName) { 51 | this.methodeQialifiedName = methodeQialifiedName; 52 | } 53 | 54 | 55 | public String getBean() { 56 | return bean; 57 | } 58 | 59 | public void setBean(String bean) { 60 | this.bean = bean; 61 | } 62 | 63 | 64 | public String getAppName() { 65 | return appName; 66 | } 67 | 68 | 69 | public void setAppName(String appName) { 70 | this.appName = appName; 71 | } 72 | 73 | 74 | public Long getTime() { 75 | return time; 76 | } 77 | 78 | 79 | public void setTime(Long time) { 80 | this.time = time; 81 | } 82 | 83 | 84 | @Override 85 | public String toString() { 86 | return "IOTimerEvent [cron=" + cron + ", methodName=" + methodName + ", methodeQialifiedName=" 87 | + methodeQialifiedName + ", bean=" + bean + ", appName=" + appName + ", time=" + time + "]"; 88 | } 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/IOResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.annotations; 18 | 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | import lombok.AllArgsConstructor; 23 | import lombok.Builder; 24 | import lombok.Data; 25 | 26 | /** 27 | * IOEvent Response annotation allows us to simplify the IOEvent response , 28 | **/ 29 | @Data 30 | @Builder 31 | @AllArgsConstructor 32 | public class IOResponse { 33 | 34 | /** 35 | * key of the IOResponse where the method will produce the event to the output 36 | * that has the same key 37 | */ 38 | private String key; 39 | /** 40 | * the body or payload to send in the event. 41 | */ 42 | private T body; 43 | 44 | /** 45 | * Map of header key and headerValue that represent a custom headers to be added 46 | * to event headers 47 | */ 48 | @Builder.Default 49 | private Map headers = new HashMap<>(); 50 | 51 | /** 52 | * create IOResponse with key and body 53 | * 54 | * @param key 55 | * @param body 56 | */ 57 | public IOResponse(String key, T body) { 58 | this.body = body; 59 | this.key = key; 60 | 61 | } 62 | 63 | /** 64 | * create IOResponse with body and custom headers 65 | * 66 | * @param body 67 | * @param headers 68 | */ 69 | public IOResponse(T body, Map headers) { 70 | this.body = body; 71 | this.headers = headers; 72 | 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/OutputEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.annotations; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * OutputEvent annotation is used to produce an event which includes a key of 26 | * the output and a topic where the event will be produced ( if the topic is not 27 | * mentioned the event will be sent to the generic topic specified in 28 | * the @IOEvent or @IFlow annotation ). 29 | **/ 30 | @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) 31 | @Retention(RetentionPolicy.RUNTIME) 32 | public @interface OutputEvent { 33 | /** 34 | * Specify the name of the output event 35 | * 36 | * @return the output event name 37 | */ 38 | String value() default ""; 39 | 40 | /** 41 | * The key of the output , which considered as output event name 42 | * 43 | * @return the output event name 44 | */ 45 | String key() default ""; 46 | 47 | /** 48 | * The topic name where to produce the event into 49 | * 50 | * @return the topic name 51 | */ 52 | String topic() default ""; 53 | 54 | /** 55 | * suffix to be add to the input key consumed to make an output key 56 | * 57 | * @return the suffix name 58 | */ 59 | String suffix() default ""; 60 | /** 61 | * Specify if the event will be sent to a manual task 62 | * 63 | */ 64 | boolean userActionRequired() default false; 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/ExceptionEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.ioevent.starter.annotations; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import java.lang.annotation.Target; 22 | 23 | /** 24 | * ExceptionEvent create a Listener which receive business or technical error 25 | * events from the current task 26 | **/ 27 | @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) 28 | @Retention(RetentionPolicy.RUNTIME) 29 | public @interface ExceptionEvent { 30 | 31 | /** 32 | * exceptions is an array of classes or interfaces that extends Throwable and 33 | * predicted to be occurred in the task method 34 | * 35 | * @return array of classes or interfaces that extends Throwable 36 | */ 37 | Class[] exception() default {}; 38 | 39 | /** 40 | * Array of @OutputEvent annotation is used to produce an event which includes a 41 | * key of the output and a topic where the event will be produced ( if the topic 42 | * is not mentioned the event will be sent to the generic topic specified in 43 | * the @IOEvent or @IFlow annotation ). 44 | * 45 | * @return Array of @OutputEvent 46 | */ 47 | OutputEvent output() default @OutputEvent(); 48 | 49 | /** 50 | * An @EndEvent annotation define the finishing point of a process with error 51 | * which includes a value where we specify the path name to the error end . 52 | * 53 | * @return an EndEvent object 54 | */ 55 | EndEvent endEvent() default @EndEvent(); 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/IOFlow.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.annotations; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * IOFlow annotation uses to specify the classes that contains @IOEvent methods 26 | * , @IOFlow classes are processed by BeanPostProcessors to extract information 27 | * from @IOEvent method , in @IOFlow we can specify the key or the name of the 28 | * flow where the class methods are part of ,name of generic topic where 29 | * the @IOEvent methods will send event if the topic wasn’t specified in 30 | * the @IOEvent annotation and apiKey which will be attached to the events of 31 | * the class methods 32 | * 33 | */ 34 | @Target({ ElementType.TYPE }) 35 | @Retention(RetentionPolicy.RUNTIME) 36 | public @interface IOFlow { 37 | /** 38 | * The name of the IOFlow 39 | * 40 | * @return the IOFlow name 41 | */ 42 | String name() default ""; 43 | 44 | /** 45 | * The topic name from where to consume events that can invoke the method or 46 | * produce event into it after running this ioevent method 47 | * 48 | * @return the topic name 49 | */ 50 | String topic() default ""; 51 | 52 | /** 53 | * 54 | * apiKey which will be attached to the events of the class methods and the BPMN 55 | * Parts generated from methods . 56 | *

57 | * Note:API key is a unique identifier used to authenticate projects and allow 58 | * to share them between IOEvent Cockpit users who owns the API key. 59 | * 60 | * @return apiKey string 61 | */ 62 | String apiKey() default ""; 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/configuration/postprocessor/BeanMethodPair.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.configuration.postprocessor; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import java.lang.reflect.Method; 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | 31 | import com.ioevent.starter.annotations.IOEvent; 32 | 33 | public class BeanMethodPair { 34 | 35 | private Object bean; 36 | private Method method; 37 | private IOEvent ioEvent; 38 | 39 | private List inputEventsArrived = new ArrayList(); 40 | public BeanMethodPair() { 41 | } 42 | 43 | public BeanMethodPair(Object bean, Method method,IOEvent ioEvent) { 44 | this.bean = bean; 45 | this.method = method; 46 | this.ioEvent=ioEvent; 47 | 48 | } 49 | 50 | public Object getBean() { 51 | return bean; 52 | } 53 | 54 | public void setBean(Object bean) { 55 | this.bean = bean; 56 | } 57 | 58 | public Method getMethod() { 59 | return method; 60 | } 61 | 62 | public void setMethod(Method method) { 63 | this.method = method; 64 | } 65 | 66 | public IOEvent getIoEvent() { 67 | return ioEvent; 68 | } 69 | 70 | public void setIoEvent(IOEvent ioEvent) { 71 | this.ioEvent = ioEvent; 72 | } 73 | 74 | public List getInputEventsArrived() { 75 | return inputEventsArrived; 76 | } 77 | public void addInputEventsArrived(String inputEvent) { 78 | this.inputEventsArrived.add(inputEvent); 79 | } 80 | public void setInputEventsArrived(List inputEventsArrived) { 81 | this.inputEventsArrived = inputEventsArrived; 82 | } 83 | 84 | @Override 85 | public String toString() { 86 | return "BeanMethodPair [bean=" + bean + ", method=" + method + /*", ioEvent=" + ioEvent + */"]"; 87 | } 88 | 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/controller/IOEventController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.controller; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | import java.util.Set; 30 | import java.util.concurrent.ExecutionException; 31 | 32 | import org.springframework.beans.factory.annotation.Autowired; 33 | import org.springframework.web.bind.annotation.CrossOrigin; 34 | import org.springframework.web.bind.annotation.GetMapping; 35 | import org.springframework.web.bind.annotation.RestController; 36 | 37 | import com.ioevent.starter.domain.IOEventBpmnPart; 38 | import com.ioevent.starter.service.TopicServices; 39 | 40 | /** 41 | * class for the controller of the IOEvent starter, 42 | */ 43 | @CrossOrigin(origins = "*") 44 | @RestController 45 | public class IOEventController { 46 | 47 | @Autowired 48 | private TopicServices topicServices; 49 | 50 | @Autowired 51 | private List iobpmnlist; 52 | 53 | @Autowired 54 | private Set apiKeys; 55 | /** 56 | * Method that return all BPMN parts of processes, 57 | * 58 | * @return list of IOEventBpmnPart Object, 59 | */ 60 | @GetMapping("/IOeventBPMN") 61 | public List getlist() { 62 | return iobpmnlist; 63 | } 64 | 65 | /** 66 | * Method that return all Topics, 67 | * 68 | * @return list of topics names, 69 | */ 70 | @GetMapping("/IOEventTopics") 71 | public List getIOEventTopics() throws InterruptedException, ExecutionException { 72 | return topicServices.getAllTopic(); 73 | } 74 | /** 75 | * Method that return all API-Keys used in application, 76 | * 77 | * @return list of API-Key names, 78 | */ 79 | @GetMapping("/IOEventApiKeys") 80 | public List getIOEventApiKeys() { 81 | return new ArrayList<>(apiKeys); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/GatewayInputEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.annotations; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * GatewayInputEvent annotation allows to determine what path is taken through a 26 | * process that controls the flow of converging Sequence Flows.a single Gateway 27 | * could have multiple @InputEvent. ,the Parallel Gateway wait until all 28 | * inputEvents to arrive so he can join them and run his method. 29 | **/ 30 | @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) 31 | @Retention(RetentionPolicy.RUNTIME) 32 | public @interface GatewayInputEvent { 33 | 34 | /** 35 | * If true the gateway type is parallel 36 | * 37 | * @return Whether it's an parallel gateway 38 | */ 39 | boolean parallel() default false; 40 | 41 | /** 42 | * If true the gateway type is exclusive 43 | * 44 | * @return Whether it's an exclusive gateway 45 | */ 46 | boolean exclusive() default true; 47 | 48 | /** 49 | * 50 | * Array of @InputEvent ,specify input as array of @InputEvent which create a 51 | * Listener for each input and receive events from the topic ( if the topic is 52 | * not mentioned it will listen to the generic topic specified in the @IOEvent 53 | * or @IFlow annotation ), and while the listener consumes an event it will 54 | * verify if the output key of the received event is equal to the @InputEvent 55 | * key in order to invoke the specific method. 56 | * 57 | * @return Array of @InputEvent 58 | */ 59 | InputEvent[] input() default {}; 60 | 61 | /** 62 | * The topic name from where to consume events that can invoke the method or 63 | * produce event into it after running this ioevent method 64 | * 65 | * @return the topic name 66 | */ 67 | String topic() default ""; 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/com/ioevent/starter/listener/IOEventParrallelListenerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.listener; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import static org.junit.Assert.assertEquals; 28 | 29 | import java.util.Arrays; 30 | import java.util.List; 31 | 32 | import org.junit.Assert; 33 | import org.junit.jupiter.api.Test; 34 | import org.mockito.InjectMocks; 35 | 36 | import com.fasterxml.jackson.core.JsonProcessingException; 37 | import com.fasterxml.jackson.databind.JsonMappingException; 38 | import com.google.gson.Gson; 39 | import com.ioevent.starter.domain.ParallelEventInfo; 40 | import com.ioevent.starter.listener.IOEventParrallelListener; 41 | 42 | class IOEventParrallelListenerTest { 43 | 44 | @InjectMocks 45 | IOEventParrallelListener ioeventParrallelListener=new IOEventParrallelListener(); 46 | 47 | Gson gson = new Gson(); 48 | @Test 49 | void parseConsumedValueTest() throws JsonMappingException, JsonProcessingException { 50 | // test String object 51 | Object string = "test String"; 52 | assertEquals(String.class, ioeventParrallelListener.parseConsumedValue(string, String.class).getClass()); 53 | // test custom object 54 | ParallelEventInfo parallelEventInfo = new ParallelEventInfo("id", 55 | Arrays.asList("first element ", "second element")); 56 | Object parallelString = gson.toJson(parallelEventInfo); 57 | assertEquals(ParallelEventInfo.class, 58 | ioeventParrallelListener.parseConsumedValue(parallelString, ParallelEventInfo.class).getClass()); 59 | 60 | } 61 | @Test 62 | void sameListTest() { 63 | List l1 = Arrays.asList("1","2","5"); 64 | List l2 = Arrays.asList("1","2","5"); 65 | List l3 = Arrays.asList("1","2"); 66 | List l4 = Arrays.asList("1","5"); 67 | Assert.assertTrue(ioeventParrallelListener.sameList(l1, l2)); 68 | Assert.assertFalse(ioeventParrallelListener.sameList(l3, l1)); 69 | Assert.assertFalse(ioeventParrallelListener.sameList(l3, l4)); 70 | Assert.assertFalse(ioeventParrallelListener.sameList(l4, l3)); 71 | 72 | 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/GatewayOutputEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.annotations; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * GatewayOutputEvent annotation allowsto determine what path is taken through a 26 | * process that controls the flow of converging Sequence Flows.a single Gateway 27 | * could have multiple output flows. GatewayOutputEvent can be divided into two 28 | * types, the Exclusive Gateway and the Parallel Gateway: for the parallel we 29 | * set the value of parallel to true and define the list of output 30 | * branches @OutputEvent where to produce the event simultaneously , for the 31 | * exclusive we set the value of exclusive to true and define the list 32 | * of @OutputEvent where the method will produce the event to the output with 33 | * the same key of the IOResponse output key . 34 | **/ 35 | @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) 36 | @Retention(RetentionPolicy.RUNTIME) 37 | public @interface GatewayOutputEvent { 38 | 39 | /** 40 | * If true the gateway type is parallel 41 | * 42 | * @return Whether it's an parallel gateway 43 | */ 44 | boolean parallel() default false; 45 | 46 | /** 47 | * If true the gateway type is exclusive 48 | * 49 | * @return Whether it's an exclusive gateway 50 | */ 51 | boolean exclusive() default true; 52 | 53 | /** 54 | * Array of @OutputEvent annotation is used to produce an event which includes a 55 | * key of the output and a topic where the event will be produced ( if the topic 56 | * is not mentioned the event will be sent to the generic topic specified in 57 | * the @IOEvent or @IFlow annotation ). 58 | * 59 | * @return Array of @OutputEvent 60 | */ 61 | OutputEvent[] output() default {}; 62 | 63 | /** 64 | * The topic name from where to consume events that can invoke the method or 65 | * produce event into it after running this ioevent method 66 | * 67 | * @return the topic name 68 | */ 69 | String topic() default ""; 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/stream/TimerStream.java: -------------------------------------------------------------------------------- 1 | package com.ioevent.starter.stream; 2 | 3 | import java.util.Date; 4 | 5 | import org.apache.kafka.common.serialization.Serdes; 6 | import org.apache.kafka.streams.KeyValue; 7 | import org.apache.kafka.streams.StreamsBuilder; 8 | import org.apache.kafka.streams.kstream.Consumed; 9 | import org.apache.kafka.streams.kstream.Grouped; 10 | import org.apache.kafka.streams.kstream.KStream; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.scheduling.TriggerContext; 14 | import org.springframework.scheduling.support.CronTrigger; 15 | import org.springframework.scheduling.support.SimpleTriggerContext; 16 | 17 | import com.google.gson.Gson; 18 | import com.ioevent.starter.domain.IOTimerEvent; 19 | import com.ioevent.starter.service.IOEventMessageBuilderService; 20 | 21 | import lombok.extern.slf4j.Slf4j; 22 | @Slf4j 23 | public class TimerStream { 24 | 25 | @Value("${spring.application.name}") 26 | private String appName; 27 | 28 | @Autowired 29 | IOEventMessageBuilderService ioeventMessageBuilderService; 30 | 31 | /** 32 | * method for processing Start-Timer events from the 33 | * ioevent-timer topic using kafka stream, 34 | * 35 | * @param builder type of StreamsBuilder, 36 | */ 37 | @Autowired 38 | public void processTimer(final StreamsBuilder builder) { 39 | 40 | Gson gson = new Gson(); 41 | 42 | KStream kstream = builder 43 | .stream("ioevent-timer", Consumed.with(Serdes.String(), Serdes.String())).map(KeyValue::new) 44 | .filter((k, v) -> { 45 | IOTimerEvent value = gson.fromJson(v, IOTimerEvent.class); 46 | return appName.equals(value.getAppName()); 47 | }); 48 | 49 | kstream.groupByKey(Grouped.with(Serdes.String(), Serdes.String())).reduce((aggValue, newValue) -> { 50 | if (aggValue == null) { 51 | return newValue; 52 | } 53 | if (checkCron(newValue, aggValue)) { 54 | IOTimerEvent newTimerEvent = gson.fromJson(newValue, IOTimerEvent.class); 55 | ioeventMessageBuilderService.sendTimerEvent(newTimerEvent, "ioevent-timer-execute"); 56 | return newValue; 57 | } 58 | 59 | return aggValue; 60 | }); 61 | 62 | 63 | } 64 | 65 | private boolean checkCron(String newValue, String aggValue) { 66 | Gson gson = new Gson(); 67 | IOTimerEvent newTimerEvent = gson.fromJson(newValue, IOTimerEvent.class); 68 | IOTimerEvent aggTimerEvent = gson.fromJson(aggValue, IOTimerEvent.class); 69 | Date lastTimerExecution = new Date(aggTimerEvent.getTime()); 70 | Date newTimerExecution = new Date(newTimerEvent.getTime()); 71 | CronTrigger cronTrigger = new CronTrigger(newTimerEvent.getCron()); 72 | TriggerContext triggerContext = new SimpleTriggerContext(lastTimerExecution, lastTimerExecution, 73 | lastTimerExecution); 74 | Date nextDate = cronTrigger.nextExecutionTime(triggerContext); 75 | return newTimerExecution.after(nextDate); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/listener/ListenerCreator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.listener; 18 | 19 | import java.lang.reflect.Method; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | import org.apache.kafka.clients.consumer.Consumer; 24 | import org.apache.kafka.clients.consumer.KafkaConsumer; 25 | import org.springframework.beans.factory.annotation.Autowired; 26 | import org.springframework.beans.factory.annotation.Value; 27 | import org.springframework.boot.autoconfigure.kafka.KafkaProperties; 28 | 29 | import com.ioevent.starter.annotations.IOEvent; 30 | import com.ioevent.starter.handler.RecordsHandler; 31 | 32 | import lombok.extern.slf4j.Slf4j; 33 | 34 | /** 35 | * class service to create listener each listener will be created on a single 36 | * thread using @Async 37 | **/ 38 | @Slf4j 39 | public class ListenerCreator { 40 | 41 | @Autowired 42 | private RecordsHandler recordsHandler; 43 | 44 | @Autowired 45 | private List listeners; 46 | 47 | @Autowired 48 | private KafkaProperties kafkaProperties; 49 | @Value("${ioevent.auto.offset.reset:earliest}") 50 | private String autoOffsetReset; 51 | /** 52 | * create listener on a single thread for the method and the topic given 53 | * 54 | * @param ioEvent 55 | */ 56 | 57 | public Listener createListener(Object bean, Method method, IOEvent ioEvent, String topicName, String groupId, 58 | Thread t1) throws Throwable { 59 | Map props = kafkaProperties.buildConsumerProperties(); 60 | props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); 61 | props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); 62 | props.put("group.id", groupId); 63 | props.put("topicName", topicName); 64 | props.put("auto.offset.reset", autoOffsetReset); 65 | 66 | 67 | Consumer consumer = new KafkaConsumer<>(props); 68 | Listener consumerApplication = new Listener(consumer, recordsHandler, bean, method, ioEvent, topicName); 69 | listeners.add(consumerApplication); 70 | 71 | synchronized (method) { 72 | 73 | method.notify(); 74 | } 75 | log.info("listener lunched for " + method); 76 | consumerApplication.runConsume(props); 77 | return consumerApplication; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/listener/IOEventTimerListener.java: -------------------------------------------------------------------------------- 1 | package com.ioevent.starter.listener; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.List; 5 | import java.util.concurrent.Executor; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.kafka.annotation.KafkaListener; 10 | import org.springframework.stereotype.Service; 11 | 12 | import com.fasterxml.jackson.core.JsonProcessingException; 13 | import com.fasterxml.jackson.databind.ObjectMapper; 14 | import com.google.gson.Gson; 15 | import com.ioevent.starter.configuration.context.AppContext; 16 | import com.ioevent.starter.domain.IOTimerEvent; 17 | import com.ioevent.starter.handler.RecordsHandler; 18 | import com.ioevent.starter.service.IOEventService; 19 | 20 | import lombok.extern.slf4j.Slf4j; 21 | 22 | @Slf4j 23 | @Service 24 | public class IOEventTimerListener { 25 | ObjectMapper mapper = new ObjectMapper(); 26 | 27 | @Autowired 28 | private List listeners; 29 | @Autowired 30 | RecordsHandler recordsHandler; 31 | @Autowired 32 | private AppContext ctx; 33 | 34 | @Autowired 35 | private IOEventService ioEventService; 36 | @Value("${spring.application.name}") 37 | private String appName; 38 | 39 | @Autowired 40 | private Executor asyncExecutor; 41 | 42 | @KafkaListener(topics = "ioevent-timer-execute", containerFactory = "userKafkaListenerFactory", groupId = "#{'${spring.kafka.consumer.group-id:${ioevent.group_id:${spring.application.name:ioevent_default_groupid}}}'}") 43 | public void consumeParallelEvent(String s) 44 | throws JsonProcessingException, ClassNotFoundException, NoSuchMethodException, SecurityException { 45 | Gson gson = new Gson(); 46 | IOTimerEvent ioeventTimerEvent = gson.fromJson(s, IOTimerEvent.class); 47 | if (ioeventTimerEvent != null && ioeventTimerEvent.getAppName().equals(appName)) { 48 | 49 | try { 50 | Object beanmObject = ctx.getApplicationContext().getBean(Class.forName(ioeventTimerEvent.getBean())); 51 | if (beanmObject != null) { 52 | asyncExecutor.execute(() -> { 53 | try { 54 | this.invokeTargetMethod(ioeventTimerEvent.getMethodName(), beanmObject, ioeventTimerEvent); 55 | } catch (Throwable e) { 56 | log.error(e.getMessage()); 57 | } 58 | }); 59 | } 60 | } catch (Exception e) { 61 | log.error(e.getMessage()); 62 | } 63 | } 64 | } 65 | 66 | /** method to invoke the method from a specific bean **/ 67 | public void invokeTargetMethod(String methodName, Object beanmObject, IOTimerEvent ioeventTimerEvent) 68 | throws Throwable { 69 | if (beanmObject != null) { 70 | 71 | for (Method met : beanmObject.getClass().getDeclaredMethods()) { 72 | if (met.getName().equals(methodName)) { 73 | Method method = met; 74 | try { 75 | method.invoke(ctx.getApplicationContext().getBean(beanmObject.getClass()), new Object[0]); 76 | } catch (Exception e) { 77 | log.error(e.getMessage()); 78 | } 79 | } 80 | } 81 | } 82 | 83 | } 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/com/ioevent/starter/service/TopicServicesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.service; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import static org.mockito.Mockito.when; 28 | 29 | import java.util.Arrays; 30 | import java.util.HashSet; 31 | import java.util.Set; 32 | import java.util.concurrent.ExecutionException; 33 | 34 | import org.apache.kafka.clients.admin.AdminClient; 35 | import org.apache.kafka.clients.admin.CreateTopicsResult; 36 | import org.apache.kafka.clients.admin.DeleteTopicsResult; 37 | import org.apache.kafka.clients.admin.ListTopicsOptions; 38 | import org.apache.kafka.clients.admin.ListTopicsResult; 39 | import org.apache.kafka.common.KafkaFuture; 40 | import org.junit.Assert; 41 | import org.junit.jupiter.api.BeforeEach; 42 | import org.junit.jupiter.api.Test; 43 | import org.mockito.InjectMocks; 44 | import org.mockito.Mock; 45 | import org.mockito.Mockito; 46 | import org.mockito.MockitoAnnotations; 47 | 48 | import com.ioevent.starter.configuration.properties.IOEventProperties; 49 | 50 | class TopicServicesTest { 51 | @InjectMocks 52 | TopicServices topicServices = new TopicServices(); 53 | @Mock 54 | AdminClient client; 55 | @Mock 56 | ListTopicsResult listTopicsResult; 57 | @Mock 58 | KafkaFuture> future; 59 | @Mock 60 | IOEventProperties iOEventProperties; 61 | @Mock 62 | CreateTopicsResult createResult; 63 | @Mock 64 | DeleteTopicsResult deleteResult; 65 | @BeforeEach 66 | public void init() { 67 | 68 | MockitoAnnotations.initMocks(this); 69 | } 70 | 71 | @Test 72 | void getAllTopicTest() throws InterruptedException, ExecutionException { 73 | when(client.listTopics(Mockito.any(ListTopicsOptions.class))).thenReturn(listTopicsResult); 74 | when(listTopicsResult.names()).thenReturn(future); 75 | when(future.get()).thenReturn(new HashSet<>(Arrays.asList("test-Topic1", "Topic2"))); 76 | when(iOEventProperties.getPrefix()).thenReturn("test"); 77 | Assert.assertEquals(Arrays.asList("test-Topic1"),topicServices.getAllTopic()); 78 | } 79 | @Test 80 | void createTopicTest() { 81 | when(client.createTopics(Mockito.anyCollection())).thenReturn(createResult); 82 | topicServices.createTopic("Topic", "test-", "3",2); 83 | Assert.assertTrue(true); 84 | 85 | } 86 | @Test 87 | void deleteTopicTest() { 88 | when(client.deleteTopics(Mockito.anyCollection())).thenReturn(deleteResult); 89 | topicServices.deleteTopic("test-Topic"); 90 | Assert.assertTrue(true); 91 | 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/service/TopicServices.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.service; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import java.util.Arrays; 28 | import java.util.List; 29 | import java.util.concurrent.ExecutionException; 30 | import java.util.stream.Collectors; 31 | 32 | import org.apache.kafka.clients.admin.AdminClient; 33 | import org.apache.kafka.clients.admin.CreateTopicsResult; 34 | import org.apache.kafka.clients.admin.ListTopicsOptions; 35 | import org.apache.kafka.clients.admin.NewTopic; 36 | import org.springframework.beans.factory.annotation.Autowired; 37 | import org.springframework.context.annotation.Primary; 38 | import org.springframework.stereotype.Service; 39 | 40 | import com.ioevent.starter.configuration.properties.IOEventProperties; 41 | 42 | import lombok.extern.slf4j.Slf4j; 43 | 44 | /** 45 | * Class TopicServices where we define services on topics (create , delete , 46 | * getAllTopics...) 47 | **/ 48 | @Slf4j 49 | @Primary 50 | @Service 51 | public class TopicServices { 52 | @Autowired 53 | private IOEventProperties iOEventProperties; 54 | 55 | @Autowired 56 | private AdminClient client; 57 | 58 | /** 59 | * get a list of all topics 60 | * 61 | * @return list of topics names, 62 | **/ 63 | public List getAllTopic() throws InterruptedException, ExecutionException { 64 | 65 | ListTopicsOptions listTopicsOptions = new ListTopicsOptions(); 66 | listTopicsOptions.listInternal(true); 67 | return client.listTopics(listTopicsOptions).names().get().stream() 68 | .filter(a -> a.startsWith(iOEventProperties.getPrefix())).collect(Collectors.toList()); 69 | } 70 | 71 | /** 72 | * create new topic named topicName 73 | * 74 | * @param topicName for the topic name, 75 | * @param replication for the replication value, 76 | * @param prefix for the ioevent prefix, 77 | **/ 78 | public void createTopic(String topicName, String prefix, String replication,int partition) { 79 | 80 | CreateTopicsResult result = client 81 | .createTopics(Arrays.asList(new NewTopic(prefix + topicName, partition, Short.valueOf(replication)))); 82 | log.info(result.toString()); 83 | } 84 | 85 | /** 86 | * Delete the topic "topicName" 87 | * 88 | * @param topicName for the topic name, 89 | **/ 90 | public void deleteTopic(String topicName) { 91 | client.deleteTopics(Arrays.asList(topicName)); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/domain/IOEventExceptionInformation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.ioevent.starter.domain; 17 | 18 | import java.util.Arrays; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | import org.apache.commons.lang3.StringUtils; 23 | 24 | import com.ioevent.starter.annotations.IOEvent; 25 | 26 | public class IOEventExceptionInformation { 27 | private Boolean errorBoundryEvent; 28 | private Map outputEvent = new HashMap<>(); 29 | private String errorType; 30 | 31 | public IOEventExceptionInformation() { 32 | super(); 33 | this.errorBoundryEvent = false; 34 | this.errorType=""; 35 | } 36 | 37 | public IOEventExceptionInformation(Boolean errorBoundryEvent, Map outputEvent) { 38 | super(); 39 | this.errorBoundryEvent = errorBoundryEvent; 40 | this.outputEvent = outputEvent; 41 | } 42 | 43 | public IOEventExceptionInformation(IOEvent ioEvent) { 44 | 45 | this.errorBoundryEvent = !ioEvent.exception().output().key().isEmpty() ; 46 | 47 | if(!StringUtils.isBlank(Arrays.toString(ioEvent.exception().exception()))) { 48 | this.errorType = Arrays.toString(ioEvent.exception().exception()); 49 | }else { 50 | this.errorType = ""; 51 | } 52 | if (StringUtils.isBlank(ioEvent.exception().endEvent().value()+ioEvent.exception().endEvent().key()) && 53 | !StringUtils.isBlank(ioEvent.exception().output().value()+ioEvent.exception().output().key()) 54 | ) { 55 | 56 | if(StringUtils.isBlank(ioEvent.exception().output().value())) { 57 | this.outputEvent.put(ioEvent.exception().output().key(), ioEvent.exception().output().topic()); 58 | }else { 59 | this.outputEvent.put(ioEvent.exception().output().value(), ioEvent.exception().output().topic()); 60 | } 61 | } 62 | } 63 | 64 | public Boolean getErrorBoundryEvent() { 65 | return errorBoundryEvent; 66 | } 67 | 68 | public void setErrorBoundryEvent(Boolean errorBoundryEvent) { 69 | this.errorBoundryEvent = errorBoundryEvent; 70 | } 71 | 72 | public Map getOutputEvent() { 73 | return outputEvent; 74 | } 75 | 76 | public void setOutputEvent(Map outputEvent) { 77 | this.outputEvent = outputEvent; 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | return "IOEventExceptionInformation [errorBoundryEvent=" + errorBoundryEvent + ", outputEvent=" + outputEvent 83 | + "]"; 84 | } 85 | 86 | public String getErrorType() { 87 | return errorType; 88 | } 89 | 90 | public void setErrorType(String errorType) { 91 | this.errorType = errorType; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/com/ioevent/starter/configuration/postprocessor/IOEventTopicBeanPostProcessorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.configuration.postprocessor; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import static org.mockito.Mockito.when; 28 | 29 | import java.util.Arrays; 30 | import java.util.HashSet; 31 | import java.util.Set; 32 | import java.util.concurrent.ExecutionException; 33 | 34 | import org.apache.kafka.clients.admin.AdminClient; 35 | import org.apache.kafka.clients.admin.CreateTopicsResult; 36 | import org.apache.kafka.clients.admin.DeleteTopicsResult; 37 | import org.apache.kafka.clients.admin.ListTopicsResult; 38 | import org.apache.kafka.common.KafkaFuture; 39 | import org.junit.Assert; 40 | import org.junit.jupiter.api.BeforeEach; 41 | import org.junit.jupiter.api.Test; 42 | import org.mockito.InjectMocks; 43 | import org.mockito.Mock; 44 | import org.mockito.MockitoAnnotations; 45 | 46 | import com.ioevent.starter.configuration.postprocessor.IOEventTopicBeanPostProcessor; 47 | import com.ioevent.starter.configuration.properties.IOEventProperties; 48 | 49 | class IOEventTopicBeanPostProcessorTest { 50 | @InjectMocks 51 | IOEventTopicBeanPostProcessor ioeventTopicBeanPostProcessor = new IOEventTopicBeanPostProcessor(); 52 | @Mock 53 | AdminClient client; 54 | @Mock 55 | ListTopicsResult listTopicsResult; 56 | @Mock 57 | KafkaFuture> future; 58 | @Mock 59 | IOEventProperties iOEventProperties; 60 | @Mock 61 | CreateTopicsResult createResult; 62 | @Mock 63 | DeleteTopicsResult deleteResult; 64 | @BeforeEach 65 | public void init() { 66 | 67 | MockitoAnnotations.initMocks(this); 68 | } 69 | @Test 70 | void topicExist_returnTrue() throws InterruptedException, ExecutionException { 71 | when(client.listTopics()).thenReturn(listTopicsResult); 72 | when(listTopicsResult.names()).thenReturn(future); 73 | when(future.get()).thenReturn(new HashSet<>(Arrays.asList("test-Topic1", "Topic2","test-Topic3"))); 74 | when(iOEventProperties.getPrefix()).thenReturn("test-"); 75 | Assert.assertTrue(ioeventTopicBeanPostProcessor.topicExist("Topic1")); 76 | } 77 | @Test 78 | void topicExist_returnFalse() throws InterruptedException, ExecutionException { 79 | when(client.listTopics()).thenReturn(listTopicsResult); 80 | when(listTopicsResult.names()).thenReturn(future); 81 | when(future.get()).thenReturn(new HashSet<>(Arrays.asList("test-Topic1", "Topic2","test-Topic3"))); 82 | when(iOEventProperties.getPrefix()).thenReturn("test-"); 83 | Assert.assertFalse(ioeventTopicBeanPostProcessor.topicExist("newTopic")); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/stream/ParallelStream.java: -------------------------------------------------------------------------------- 1 | package com.ioevent.starter.stream; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.Stream; 8 | 9 | import org.apache.kafka.common.serialization.Serdes; 10 | import org.apache.kafka.streams.KeyValue; 11 | import org.apache.kafka.streams.StreamsBuilder; 12 | import org.apache.kafka.streams.kstream.Consumed; 13 | import org.apache.kafka.streams.kstream.Grouped; 14 | import org.apache.kafka.streams.kstream.KStream; 15 | import org.apache.kafka.streams.kstream.Produced; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.beans.factory.annotation.Value; 18 | 19 | import com.google.gson.Gson; 20 | import com.ioevent.starter.domain.IOEventParallelEventInformation; 21 | 22 | import lombok.extern.slf4j.Slf4j; 23 | 24 | @Slf4j 25 | public class ParallelStream { 26 | 27 | 28 | @Value("${spring.application.name}") 29 | private String appName; 30 | 31 | /** 32 | * method for processing parallel events from the 33 | * ioevent-parallel-gateway-events topic using kafka stream, 34 | * 35 | * @param builder type of StreamsBuilder, 36 | */ 37 | @Autowired 38 | public void processKStream(final StreamsBuilder builder) { 39 | 40 | Gson gson = new Gson(); 41 | 42 | KStream kstream = builder 43 | .stream("ioevent-parallel-gateway-events", Consumed.with(Serdes.String(), Serdes.String())) 44 | .map(KeyValue::new).filter((k, v) -> { 45 | IOEventParallelEventInformation value = gson.fromJson(v, IOEventParallelEventInformation.class); 46 | return appName.equals(value.getHeaders().get("AppName")); 47 | }); 48 | kstream.groupByKey(Grouped.with(Serdes.String(), Serdes.String())) 49 | .aggregate(() -> "", (key, value, aggregateValue) -> { 50 | IOEventParallelEventInformation currentValue = gson.fromJson(value, 51 | IOEventParallelEventInformation.class); 52 | IOEventParallelEventInformation updatedValue; 53 | if (!aggregateValue.isBlank()) { 54 | updatedValue = gson.fromJson(aggregateValue, IOEventParallelEventInformation.class); 55 | } else { 56 | updatedValue = currentValue; 57 | } 58 | List updatedOutputList = Stream 59 | .of(currentValue.getInputsArrived(), updatedValue.getInputsArrived()) 60 | .flatMap(Collection::stream).distinct().collect(Collectors.toList()); 61 | Map updatedHeaders = Stream.of(currentValue.getHeaders(), updatedValue.getHeaders()) 62 | .flatMap(map -> map.entrySet().stream()) 63 | .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1)); 64 | Map updatedPayload = Stream 65 | .of(currentValue.getPayloadMap(), updatedValue.getPayloadMap()) 66 | .flatMap(map -> map.entrySet().stream()) 67 | .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1)); 68 | updatedValue.setInputsArrived(updatedOutputList); 69 | updatedValue.setHeaders(updatedHeaders); 70 | updatedValue.setPayloadMap(updatedPayload); 71 | aggregateValue = gson.toJson(updatedValue); 72 | return aggregateValue; 73 | }).toStream() 74 | .to("ioevent-parallel-gateway-aggregation", Produced.with(Serdes.String(), Serdes.String())); 75 | 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | 3 | volumes: 4 | zookeeper-data: 5 | driver: local 6 | zookeeper-log: 7 | driver: local 8 | kafka-data: 9 | driver: local 10 | 11 | services: 12 | 13 | zookeeper: 14 | restart: always 15 | image: confluentinc/cp-zookeeper 16 | volumes: 17 | - zookeeper-data:/var/lib/zookeeper/data:Z 18 | - zookeeper-log:/var/lib/zookeeper/log:Z 19 | environment: 20 | ZOOKEEPER_CLIENT_PORT: '2181' 21 | ZOOKEEPER_ADMIN_ENABLE_SERVER: 'false' 22 | 23 | kafka: 24 | restart: always 25 | image: confluentinc/cp-kafka 26 | container_name: kafka 27 | volumes: 28 | - kafka-data:/var/lib/kafka:Z 29 | ports: 30 | - "9092:9092" 31 | - "29092:29092" 32 | environment: 33 | KAFKA_BROKER_ID: '0' 34 | KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181' 35 | KAFKA_NUM_PARTITIONS: '12' 36 | KAFKA_COMPRESSION_TYPE: 'gzip' 37 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: '1' 38 | KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: '1' 39 | KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: '1' 40 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT 41 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092 42 | KAFKA_CONFLUENT_SUPPORT_METRICS_ENABLE: 'false' 43 | KAFKA_JMX_PORT: '9091' 44 | KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true' 45 | KAFKA_AUTHORIZER_CLASS_NAME: 'kafka.security.auth.SimpleAclAuthorizer' 46 | KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: 'true' 47 | links: 48 | - zookeeper 49 | 50 | schema-registry: 51 | image: confluentinc/cp-schema-registry 52 | depends_on: 53 | - kafka 54 | environment: 55 | SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: 'PLAINTEXT://kafka:9092' 56 | SCHEMA_REGISTRY_HOST_NAME: 'schema-registry' 57 | SCHEMA_REGISTRY_LISTENERS: 'http://0.0.0.0:8085' 58 | SCHEMA_REGISTRY_LOG4J_ROOT_LOGLEVEL: 'INFO' 59 | 60 | kafkahq: 61 | restart: always 62 | image: tchiotludo/akhq 63 | environment: 64 | AKHQ_CONFIGURATION: | 65 | akhq: 66 | connections: 67 | docker-kafka-server: 68 | properties: 69 | bootstrap.servers: "kafka:9092" 70 | schema-registry: 71 | url: "http://schema-registry:8085" 72 | connect: 73 | - name: "connect" 74 | url: "http://connect:8083" 75 | 76 | ports: 77 | - 18080:8080 78 | links: 79 | - kafka 80 | - schema-registry 81 | ksqldb-server: 82 | restart: always 83 | image: confluentinc/ksqldb-server:0.18.0 84 | hostname: ksqldb-server 85 | container_name: ksqldb-server 86 | depends_on: 87 | - kafka 88 | ports: 89 | - "8088:8088" 90 | environment: 91 | KSQL_LISTENERS: http://0.0.0.0:8088 92 | KSQL_BOOTSTRAP_SERVERS: kafka:9092 93 | KSQL_KSQL_LOGGING_PROCESSING_STREAM_AUTO_CREATE: "true" 94 | KSQL_KSQL_LOGGING_PROCESSING_TOPIC_AUTO_CREATE: "true" 95 | 96 | ksqldb-cli: 97 | restart: always 98 | image: confluentinc/ksqldb-cli:0.18.0 99 | container_name: ksqldb-cli 100 | depends_on: 101 | - kafka 102 | - ksqldb-server 103 | entrypoint: /bin/sh 104 | tty: true 105 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/stream/MessageStream.java: -------------------------------------------------------------------------------- 1 | package com.ioevent.starter.stream; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.Stream; 8 | 9 | import org.apache.kafka.common.serialization.Serdes; 10 | import org.apache.kafka.streams.KeyValue; 11 | import org.apache.kafka.streams.StreamsBuilder; 12 | import org.apache.kafka.streams.kstream.Consumed; 13 | import org.apache.kafka.streams.kstream.Grouped; 14 | import org.apache.kafka.streams.kstream.KStream; 15 | import org.apache.kafka.streams.kstream.Produced; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.beans.factory.annotation.Value; 18 | 19 | import com.google.gson.Gson; 20 | import com.ioevent.starter.domain.IOEventMessageEventInformation; 21 | 22 | public class MessageStream { 23 | 24 | @Value("${spring.application.name}") 25 | private String appName; 26 | 27 | /** 28 | * method for processing parallel events from the 29 | * ioevent-parallel-gateway-events topic using kafka stream, 30 | * 31 | * @param builder type of StreamsBuilder, 32 | */ 33 | @Autowired 34 | public void processMessage(final StreamsBuilder builder) { 35 | 36 | Gson gson = new Gson(); 37 | 38 | KStream kstream = builder 39 | .stream("ioevent-message-events", Consumed.with(Serdes.String(), Serdes.String())).map(KeyValue::new) 40 | .filter((k, v) -> { 41 | IOEventMessageEventInformation value = gson.fromJson(v, IOEventMessageEventInformation.class); 42 | return appName.equals(value.getHeaders().get("AppName")); 43 | }); 44 | kstream.groupByKey(Grouped.with(Serdes.String(), Serdes.String())) 45 | .aggregate(() -> "", (key, value, aggregateValue) -> { 46 | IOEventMessageEventInformation currentValue = gson.fromJson(value, 47 | IOEventMessageEventInformation.class); 48 | IOEventMessageEventInformation updatedValue; 49 | if (!aggregateValue.isBlank()) { 50 | updatedValue = gson.fromJson(aggregateValue, IOEventMessageEventInformation.class); 51 | } else { 52 | updatedValue = currentValue; 53 | } 54 | List updatedOutputList = Stream 55 | .of(currentValue.getInputsArrived(), updatedValue.getInputsArrived()) 56 | .flatMap(Collection::stream).distinct().collect(Collectors.toList()); 57 | Map updatedHeaders = Stream.of(currentValue.getHeaders(), updatedValue.getHeaders()) 58 | .flatMap(map -> map.entrySet().stream()) 59 | .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1)); 60 | Map updatedPayload = Stream 61 | .of(currentValue.getPayloadMap(), updatedValue.getPayloadMap()) 62 | .flatMap(map -> map.entrySet().stream()) 63 | .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1)); 64 | if (currentValue.getMessageEventArrived().equals(currentValue.getMessageEventRequired())) { 65 | updatedValue.setMessageEventArrived(currentValue.getMessageEventArrived()); 66 | } 67 | updatedOutputList.retainAll(currentValue.getInputRequired()); 68 | updatedValue.setInputsArrived(updatedOutputList); 69 | updatedValue.setHeaders(updatedHeaders); 70 | updatedValue.setPayloadMap(updatedPayload); 71 | 72 | aggregateValue = gson.toJson(updatedValue); 73 | return aggregateValue; 74 | }).toStream() 75 | .to("ioevent-event-message-aggregation", Produced.with(Serdes.String(), Serdes.String())); 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/service/IOEventRegistryService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.service; 18 | 19 | import java.net.InetAddress; 20 | import java.net.UnknownHostException; 21 | import java.util.List; 22 | import java.util.Set; 23 | import java.util.UUID; 24 | import java.util.concurrent.ExecutionException; 25 | import java.util.stream.Collectors; 26 | 27 | //import javax.annotation.PreDestroy; 28 | 29 | import jakarta.annotation.PreDestroy; 30 | import org.springframework.beans.factory.annotation.Autowired; 31 | import org.springframework.beans.factory.annotation.Value; 32 | import org.springframework.boot.web.context.WebServerInitializedEvent; 33 | import org.springframework.context.ApplicationListener; 34 | import org.springframework.kafka.core.KafkaTemplate; 35 | import org.springframework.kafka.support.KafkaHeaders; 36 | import org.springframework.messaging.Message; 37 | import org.springframework.messaging.support.MessageBuilder; 38 | import org.springframework.scheduling.annotation.Scheduled; 39 | 40 | import com.ioevent.starter.domain.IOEventBpmnPart; 41 | import com.ioevent.starter.domain.RegistryAction; 42 | 43 | import lombok.extern.slf4j.Slf4j; 44 | 45 | @Slf4j 46 | public class IOEventRegistryService implements ApplicationListener { 47 | @Autowired 48 | private TopicServices topicServices; 49 | 50 | @Autowired 51 | private List iobpmnlist; 52 | 53 | @Autowired 54 | private Set apiKeys; 55 | 56 | @Value("${spring.application.name}") 57 | private String appName; 58 | @Autowired 59 | private Set ioTopics; 60 | @Autowired 61 | private KafkaTemplate kafkaTemplate; 62 | @Autowired 63 | private UUID instanceId; 64 | private int port; 65 | 66 | @PreDestroy 67 | public void shutdownHook() throws InterruptedException, ExecutionException, UnknownHostException { 68 | Message> message = MessageBuilder.withPayload(iobpmnlist) 69 | .setHeader(KafkaHeaders.TOPIC, "ioevent-apps").setHeader(KafkaHeaders.KEY, appName) 70 | .setHeader("IO-APP-NAME", appName).setHeader("APIKEYS", apiKeys) 71 | .setHeader("INSTANCE-ID", instanceId.toString()) 72 | .setHeader("IO-APP-HOST", InetAddress.getLocalHost().getHostAddress()).setHeader("IO-APP-PORT", port) 73 | .setHeader("TOPICS", ioTopics.stream().collect(Collectors.toList())).setHeader("ACTION", RegistryAction.CLOSE.toString()) 74 | .build(); 75 | kafkaTemplate.send(message); 76 | } 77 | 78 | @Scheduled(fixedRate = 30000) 79 | public void registryHeartBeat() throws InterruptedException, ExecutionException, UnknownHostException { 80 | Message> message = MessageBuilder.withPayload(iobpmnlist) 81 | .setHeader(KafkaHeaders.TOPIC, "ioevent-apps").setHeader(KafkaHeaders.KEY, appName) 82 | .setHeader("IO-APP-NAME", appName).setHeader("INSTANCE-ID", instanceId.toString()) 83 | .setHeader("IO-APP-HOST", InetAddress.getLocalHost().getHostAddress()).setHeader("IO-APP-PORT", port) 84 | .setHeader("APIKEYS", apiKeys).setHeader("TOPICS", ioTopics.stream().collect(Collectors.toList())) 85 | .setHeader("ACTION", RegistryAction.REGISTER.toString()).build(); 86 | kafkaTemplate.send(message); 87 | 88 | } 89 | 90 | @Override 91 | public void onApplicationEvent(WebServerInitializedEvent event) { 92 | this.port = event.getWebServer().getPort(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/configuration/properties/IOEventProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.configuration.properties; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import java.util.List; 28 | import java.util.logging.Logger; 29 | 30 | import org.springframework.boot.context.properties.ConfigurationProperties; 31 | import org.springframework.context.annotation.Configuration; 32 | 33 | /** 34 | * Class for ioevent properties that can be specified in the properties file : 35 | * - topic_names : list of topics that the user want to create, 36 | * - prefix : prefix for the topics default "IOEvent-", 37 | * - group_id : group id for kafka consumer, 38 | * - auto_create_topic : create the topics used in code automatically if true. 39 | **/ 40 | @Configuration 41 | @ConfigurationProperties(prefix = "ioevent") 42 | public class IOEventProperties { 43 | 44 | 45 | 46 | private List topic_names; 47 | 48 | private String prefix = ""; 49 | 50 | private String group_id= "ioevent"; 51 | private Boolean auto_create_topic = true; 52 | private String topic_replication="1" ; 53 | private String api_key = ""; 54 | private Long heartBeat = 30000L ; 55 | private int consumers_per_topic = 0; 56 | 57 | public int getConsumers_per_topic() { 58 | return consumers_per_topic; 59 | } 60 | 61 | public void setConsumers_per_topic(int consumers_per_topic) { 62 | this.consumers_per_topic = consumers_per_topic; 63 | } 64 | 65 | private int core_pool_size = 2 ; 66 | 67 | public Long getHeartBeat() { 68 | return heartBeat; 69 | } 70 | 71 | public void setHeartBeat(Long heartBeat) { 72 | this.heartBeat = heartBeat; 73 | } 74 | 75 | private int topic_partition=1; 76 | public String getPrefix() { 77 | return prefix; 78 | } 79 | 80 | public void setPrefix(String prefix) { 81 | this.prefix = prefix + "-"; 82 | } 83 | 84 | public List getTopic_names() { 85 | return topic_names; 86 | } 87 | 88 | public void setTopic_names(List topic_names) { 89 | this.topic_names = topic_names; 90 | } 91 | 92 | 93 | public String getTopicReplication() { 94 | return topic_replication; 95 | } 96 | 97 | public void setTopicReplication(String topicReplication) { 98 | this.topic_replication = topicReplication; 99 | } 100 | 101 | public String getGroup_id() { 102 | return group_id; 103 | } 104 | 105 | public void setGroup_id(String group_id) { 106 | this.group_id = group_id; 107 | } 108 | 109 | public Boolean getAuto_create_topic() { 110 | return auto_create_topic; 111 | } 112 | 113 | public void setAuto_create_topic(Boolean auto_create_topic) { 114 | this.auto_create_topic = auto_create_topic; 115 | } 116 | 117 | public String getApikey() { 118 | return api_key; 119 | } 120 | 121 | public int getTopic_partition() { 122 | return topic_partition; 123 | } 124 | 125 | public void setTopic_partition(int topic_partition) { 126 | this.topic_partition = topic_partition; 127 | } 128 | 129 | public void setApikey(String api_key) { 130 | this.api_key = api_key; 131 | } 132 | 133 | public void logProp() { 134 | Logger LOGGER = Logger.getLogger(Thread.currentThread().getStackTrace()[0].getClassName()); 135 | 136 | LOGGER.info("prp" + this.topic_names); 137 | } 138 | 139 | // standard getters and setters 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/domain/IOEventMessageEventInformation.java: -------------------------------------------------------------------------------- 1 | package com.ioevent.starter.domain; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import org.apache.kafka.clients.consumer.ConsumerRecord; 9 | 10 | import com.ioevent.starter.configuration.postprocessor.BeanMethodPair; 11 | import com.ioevent.starter.handler.IOEventRecordInfo; 12 | 13 | public class IOEventMessageEventInformation { 14 | 15 | private String value; 16 | private Map payloadMap = new HashMap<>(); 17 | private String listenerTopic; 18 | private String method; 19 | private String className; 20 | private Map headers = new HashMap<>(); 21 | private List inputRequired; 22 | private List inputsArrived = new ArrayList<>(); 23 | private String messageEventRequired ; 24 | private String messageEventArrived ; 25 | 26 | 27 | 28 | 29 | public IOEventMessageEventInformation(ConsumerRecord consumerRecord, 30 | IOEventRecordInfo ioeventRecordInfo, BeanMethodPair pair, List inputRequired, String appName, 31 | String messageKey) { 32 | super(); 33 | this.value = consumerRecord.value(); 34 | this.payloadMap.put(ioeventRecordInfo.getOutputConsumedName(), consumerRecord.value()); 35 | this.inputsArrived.add(ioeventRecordInfo.getOutputConsumedName()); 36 | this.listenerTopic = consumerRecord.topic(); 37 | this.method = pair.getMethod().getName(); 38 | this.className = pair.getBean().getClass().getName(); 39 | this.inputRequired = inputRequired; 40 | headers.put("AppName", appName); 41 | consumerRecord.headers().forEach(header -> this.headers.put(header.key(), new String(header.value()))); 42 | this.messageEventRequired = pair.getIoEvent().message().key(); 43 | this.messageEventArrived = ioeventRecordInfo.getMessageKey(); 44 | } 45 | public IOEventMessageEventInformation() { 46 | super(); 47 | } 48 | public String getValue() { 49 | return value; 50 | } 51 | public void setValue(String value) { 52 | this.value = value; 53 | } 54 | public Map getPayloadMap() { 55 | return payloadMap; 56 | } 57 | public void setPayloadMap(Map payloadMap) { 58 | this.payloadMap = payloadMap; 59 | } 60 | public String getListenerTopic() { 61 | return listenerTopic; 62 | } 63 | public void setListenerTopic(String listenerTopic) { 64 | this.listenerTopic = listenerTopic; 65 | } 66 | public String getMethod() { 67 | return method; 68 | } 69 | public void setMethod(String method) { 70 | this.method = method; 71 | } 72 | public String getClassName() { 73 | return className; 74 | } 75 | public void setClassName(String className) { 76 | this.className = className; 77 | } 78 | public Map getHeaders() { 79 | return headers; 80 | } 81 | public void setHeaders(Map headers) { 82 | this.headers = headers; 83 | } 84 | public List getInputRequired() { 85 | return inputRequired; 86 | } 87 | public void setInputRequired(List inputRequired) { 88 | this.inputRequired = inputRequired; 89 | } 90 | public List getInputsArrived() { 91 | return inputsArrived; 92 | } 93 | public void setInputsArrived(List inputsArrived) { 94 | this.inputsArrived = inputsArrived; 95 | } 96 | public String getMessageEventRequired() { 97 | return messageEventRequired; 98 | } 99 | public void setMessageEventRequired(String messageEventRequired) { 100 | this.messageEventRequired = messageEventRequired; 101 | } 102 | public String getMessageEventArrived() { 103 | return messageEventArrived; 104 | } 105 | public void setMessageEventArrived(String messageEventArrived) { 106 | this.messageEventArrived = messageEventArrived; 107 | } 108 | @Override 109 | public String toString() { 110 | return "IOEventMessageEventInformation [value=" + value + ", payloadMap=" + payloadMap + ", listenerTopic=" 111 | + listenerTopic + ", method=" + method + ", className=" + className + ", headers=" + headers 112 | + ", inputRequired=" + inputRequired + ", inputsArrived=" + inputsArrived + ", messageEventRequired=" 113 | + messageEventRequired + ", messageEventArrived=" + messageEventArrived + "]"; 114 | } 115 | 116 | 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/listener/Listener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.listener; 18 | 19 | 20 | 21 | 22 | import java.lang.reflect.Method; 23 | import java.time.Duration; 24 | import java.util.*; 25 | 26 | import org.apache.kafka.clients.consumer.Consumer; 27 | import org.apache.kafka.clients.consumer.ConsumerRecords; 28 | 29 | import com.ioevent.starter.annotations.IOEvent; 30 | import com.ioevent.starter.configuration.postprocessor.BeanMethodPair; 31 | import com.ioevent.starter.handler.RecordsHandler; 32 | 33 | import lombok.extern.slf4j.Slf4j; 34 | 35 | /** 36 | * Listener to consume from topic and send the records consumed to the Records 37 | * Handler 38 | */ 39 | 40 | @Slf4j 41 | public class Listener { 42 | 43 | private volatile boolean keepConsuming = true; 44 | final private RecordsHandler recordsHandler; 45 | private Consumer consumer; 46 | private Object bean; 47 | private Method method; 48 | private String topic; 49 | private List beanMethodPairs = new ArrayList<>(); 50 | 51 | /** 52 | * listener constructor 53 | * 54 | * @param ioEvent 55 | * @param topicName 56 | */ 57 | public Listener(final Consumer consumer, final RecordsHandler recordsHandler, Object bean, 58 | Method method, IOEvent ioEvent, String topicName) { 59 | this.consumer = consumer; 60 | this.recordsHandler = recordsHandler; 61 | this.bean = bean; 62 | this.method = method; 63 | this.topic = topicName; 64 | this.beanMethodPairs.add(new BeanMethodPair(bean, method, ioEvent)); 65 | 66 | } 67 | 68 | /** 69 | * run consumer to subscribe to the output topic and start consuming ,as soon as 70 | * we get a record we send the record to the handler 71 | **/ 72 | public void runConsume(final Map consumerProps) throws Throwable { 73 | try { 74 | consumer.subscribe(Collections.singletonList(consumerProps.get("topicName").toString())); 75 | while (keepConsuming) { 76 | ConsumerRecords consumerRecords = consumer.poll(Duration.ofMillis(10)); 77 | if (!consumerRecords.isEmpty()) { 78 | recordsHandler.process(consumerRecords, this.beanMethodPairs); 79 | } 80 | } 81 | 82 | } finally { 83 | consumer.close(); 84 | } 85 | } 86 | 87 | public void shutdown() { 88 | keepConsuming = false; 89 | } 90 | 91 | public Object getBean() { 92 | return bean; 93 | } 94 | 95 | public void setBean(Object bean) { 96 | this.bean = bean; 97 | } 98 | 99 | public Method getMethod() { 100 | return method; 101 | } 102 | 103 | public void setMethod(Method method) { 104 | this.method = method; 105 | } 106 | 107 | public String getTopic() { 108 | return topic; 109 | } 110 | 111 | public void setTopic(String topic) { 112 | this.topic = topic; 113 | } 114 | 115 | public List getBeanMethodPairs() { 116 | return beanMethodPairs; 117 | } 118 | 119 | public void setBeanMethodPairs(List beanMethodPairs) { 120 | this.beanMethodPairs = beanMethodPairs; 121 | } 122 | 123 | public void addBeanMethod(BeanMethodPair beanMethod) { 124 | boolean valid = true; 125 | for (BeanMethodPair beanMethodPair : beanMethodPairs) { 126 | if ((beanMethod.getBean().equals(beanMethodPair.getBean()) 127 | && beanMethod.getMethod().equals(beanMethodPair.getMethod()))) { 128 | valid = false; 129 | } 130 | } 131 | if (valid) { 132 | this.beanMethodPairs.add(beanMethod); 133 | 134 | } 135 | 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/listener/MessageListener.java: -------------------------------------------------------------------------------- 1 | package com.ioevent.starter.listener; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.List; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.kafka.annotation.KafkaListener; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.util.StopWatch; 10 | 11 | import com.fasterxml.jackson.core.JsonProcessingException; 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | import com.google.gson.Gson; 14 | import com.ioevent.starter.configuration.context.AppContext; 15 | import com.ioevent.starter.domain.IOEventHeaders; 16 | import com.ioevent.starter.domain.IOEventMessageEventInformation; 17 | import com.ioevent.starter.handler.IOEventRecordInfo; 18 | import com.ioevent.starter.handler.RecordsHandler; 19 | import com.ioevent.starter.service.IOEventContextHolder; 20 | import com.ioevent.starter.service.IOEventService; 21 | 22 | import lombok.extern.slf4j.Slf4j; 23 | 24 | @Slf4j 25 | @Service 26 | public class MessageListener { 27 | ObjectMapper mapper = new ObjectMapper(); 28 | 29 | @Autowired 30 | private List listeners; 31 | @Autowired 32 | RecordsHandler recordsHandler; 33 | @Autowired 34 | private AppContext ctx; 35 | 36 | @Autowired 37 | private IOEventService ioEventService; 38 | 39 | @KafkaListener(topics = "ioevent-event-message-aggregation", containerFactory = "userKafkaListenerFactory", groupId = "#{'${spring.kafka.consumer.group-id:${ioevent.group_id:${spring.application.name:ioevent_default_groupid}}}'}") 40 | public void consumeMessageEvent(String s) 41 | throws JsonProcessingException, ClassNotFoundException, NoSuchMethodException, SecurityException { 42 | Gson gson = new Gson(); 43 | IOEventMessageEventInformation iOEventMessageEventInformation = gson.fromJson(s, 44 | IOEventMessageEventInformation.class); 45 | if (iOEventMessageEventInformation != null && validMessage(iOEventMessageEventInformation)) { 46 | try { 47 | Object beanmObject = ctx.getApplicationContext() 48 | .getBean(Class.forName(iOEventMessageEventInformation.getClassName())); 49 | if (beanmObject != null) { 50 | new Thread(() -> { 51 | StopWatch watch = new StopWatch(); 52 | watch.start((String) iOEventMessageEventInformation.getHeaders() 53 | .get(IOEventHeaders.CORRELATION_ID.toString())); 54 | IOEventRecordInfo ioeventRecordInfo = new IOEventRecordInfo( 55 | iOEventMessageEventInformation.getHeaders() 56 | .get(IOEventHeaders.CORRELATION_ID.toString()).toString(), 57 | iOEventMessageEventInformation.getHeaders().get(IOEventHeaders.PROCESS_NAME.toString()) 58 | .toString(), 59 | iOEventMessageEventInformation.getInputsArrived().toString(), watch, 60 | Long.valueOf(iOEventMessageEventInformation.getHeaders() 61 | .get(IOEventHeaders.START_INSTANCE_TIME.toString()).toString()), 62 | null); 63 | IOEventContextHolder.setContext(ioeventRecordInfo); 64 | 65 | try { 66 | invokeTargetMethod(iOEventMessageEventInformation.getMethod(), beanmObject, 67 | iOEventMessageEventInformation); 68 | } catch (Throwable e) { 69 | log.error(e.getMessage()); 70 | } 71 | }).start(); 72 | } 73 | } catch (Throwable e) { 74 | log.error("error while invoking method "); 75 | } 76 | 77 | } else { 78 | log.info("Message Event Not Completed, output arrived : " + // 79 | iOEventMessageEventInformation.getInputsArrived() + "message key arrived " 80 | + iOEventMessageEventInformation.getMessageEventArrived()); 81 | } 82 | 83 | } 84 | 85 | public boolean validMessage(IOEventMessageEventInformation iOEventMessageEventInformation) { 86 | return sameList(iOEventMessageEventInformation.getInputRequired(), 87 | iOEventMessageEventInformation.getInputsArrived()) 88 | && iOEventMessageEventInformation.getMessageEventArrived() 89 | .equals(iOEventMessageEventInformation.getMessageEventRequired()); 90 | } 91 | 92 | /** method to invoke the method from a specific bean **/ 93 | public void invokeTargetMethod(String methodName, Object beanmObject, 94 | IOEventMessageEventInformation messageEventInformation) throws Throwable { 95 | if (beanmObject != null) { 96 | 97 | for (Method met : beanmObject.getClass().getDeclaredMethods()) { 98 | if (met.getName().equals(methodName)) { 99 | Method method = met; 100 | try { 101 | method.invoke(ctx.getApplicationContext().getBean(beanmObject.getClass()), new Object[0]); 102 | } catch (Exception e) { 103 | log.error(e.getMessage()); 104 | } 105 | } 106 | } 107 | } 108 | } 109 | 110 | public boolean sameList(List firstList, List secondList) { 111 | return (firstList.size() == secondList.size() && firstList.containsAll(secondList) 112 | && secondList.containsAll(firstList)); 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/domain/IOEventGatwayInformation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.domain; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import java.util.HashMap; 28 | import java.util.Map; 29 | 30 | import org.apache.commons.lang3.StringUtils; 31 | 32 | import com.ioevent.starter.annotations.IOEvent; 33 | import com.ioevent.starter.annotations.InputEvent; 34 | import com.ioevent.starter.annotations.OutputEvent; 35 | 36 | /** 37 | * class for @IOEvent annotation gateway information that will be send within 38 | * the BPMN Parts to the Admin, it contains information of the type of the 39 | * gateway also the input events and output events of the gateway. 40 | **/ 41 | public class IOEventGatwayInformation { 42 | 43 | private Boolean exclusiveInput = true; 44 | private Boolean parallelInput = false; 45 | private Boolean exclusiveOutput = true; 46 | private Boolean parallelOutput = false ; 47 | private Map inputEvent = new HashMap<>(); 48 | private Map outputEvent = new HashMap<>(); 49 | 50 | public IOEventGatwayInformation() { 51 | } 52 | 53 | public IOEventGatwayInformation(Boolean exclusiveInput, Boolean parallelInput, Boolean exclusiveOutput, 54 | Boolean parallelOutput, Map inputEvent, Map outputEvent) { 55 | this.exclusiveInput = exclusiveInput; 56 | this.parallelInput = parallelInput; 57 | this.exclusiveOutput = exclusiveOutput; 58 | this.parallelOutput = parallelOutput; 59 | this.inputEvent = inputEvent; 60 | this.outputEvent = outputEvent; 61 | } 62 | 63 | public IOEventGatwayInformation(IOEvent ioEvent) { 64 | 65 | this.exclusiveInput = ioEvent.gatewayInput().exclusive(); 66 | this.parallelInput = ioEvent.gatewayInput().parallel(); 67 | this.exclusiveOutput = ioEvent.gatewayOutput().exclusive(); 68 | this.parallelOutput = ioEvent.gatewayOutput().parallel(); 69 | this.inputEvent = this.addInput(ioEvent); 70 | this.outputEvent = this.addOutput(ioEvent); 71 | } 72 | 73 | public Boolean getExclusiveInput() { 74 | return exclusiveInput; 75 | } 76 | 77 | public void setExclusiveInput(Boolean exclusiveInput) { 78 | this.exclusiveInput = exclusiveInput; 79 | } 80 | 81 | public Boolean getParallelInput() { 82 | return parallelInput; 83 | } 84 | 85 | public void setParallelInput(Boolean parallelInput) { 86 | this.parallelInput = parallelInput; 87 | } 88 | 89 | public Boolean getExclusiveOutput() { 90 | return exclusiveOutput; 91 | } 92 | 93 | public void setExclusiveOutput(Boolean exclusiveOutput) { 94 | this.exclusiveOutput = exclusiveOutput; 95 | } 96 | 97 | public Boolean getParallelOutput() { 98 | return parallelOutput; 99 | } 100 | 101 | public void setParallelOutput(Boolean parallelOutput) { 102 | this.parallelOutput = parallelOutput; 103 | } 104 | 105 | public Map getInputEvent() { 106 | return inputEvent; 107 | } 108 | 109 | public void setInputEvent(Map inputEvent) { 110 | this.inputEvent = inputEvent; 111 | } 112 | 113 | public Map getOutputEvent() { 114 | return outputEvent; 115 | } 116 | 117 | public void setOutputEvent(Map outputEvent) { 118 | this.outputEvent = outputEvent; 119 | } 120 | 121 | public Map addInput(IOEvent ioEvent) { 122 | Map result = new HashMap<>(); 123 | for (InputEvent input : ioEvent.gatewayInput().input()) { 124 | if (!StringUtils.isBlank(input.key() + input.value())) { 125 | 126 | if (!StringUtils.isBlank(input.value())) { 127 | result.put(input.value(), input.topic()); 128 | } else { 129 | result.put(input.key(), input.topic()); 130 | } 131 | 132 | } 133 | 134 | } 135 | 136 | return result; 137 | } 138 | 139 | public Map addOutput(IOEvent ioEvent) { 140 | Map result = new HashMap<>(); 141 | for (OutputEvent output : ioEvent.gatewayOutput().output()) { 142 | if (!StringUtils.isBlank(output.key() + output.value())) { 143 | 144 | if (!StringUtils.isBlank(output.value())) { 145 | result.put(output.value(), output.topic()); 146 | } else { 147 | result.put(output.key(), output.topic()); 148 | } 149 | 150 | } 151 | } 152 | return result; 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/handler/IOEventRecordInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.handler; 18 | 19 | import java.util.List; 20 | 21 | import org.apache.kafka.common.header.Header; 22 | import org.springframework.util.StopWatch; 23 | 24 | /** 25 | * class for the record event information consumed from event 26 | */ 27 | public class IOEventRecordInfo { 28 | private String id; 29 | private String workFlowName; 30 | private String outputConsumedName; 31 | private List

headerList; 32 | private StopWatch watch; 33 | private Long instanceStartTime; 34 | private Long startTime = System.currentTimeMillis(); 35 | private String lastEventEndTime; 36 | private String body; 37 | private String MessageKey; 38 | private String taskType; 39 | 40 | public IOEventRecordInfo() { 41 | } 42 | 43 | public IOEventRecordInfo(String id, String workFlowName, String outputConsumedName, StopWatch watch, 44 | Long instanceStartTime, String lastEventEndTime) { 45 | this.id = id; 46 | this.workFlowName = workFlowName; 47 | this.outputConsumedName = outputConsumedName; 48 | this.watch = watch; 49 | this.instanceStartTime = instanceStartTime; 50 | this.lastEventEndTime = lastEventEndTime; 51 | } 52 | 53 | public IOEventRecordInfo(String id, String workFlowName, String outputConsumedName, List
headerList, 54 | Long instanceStartTime, String lastEventEndTime) { 55 | super(); 56 | this.id = id; 57 | this.workFlowName = workFlowName; 58 | this.outputConsumedName = outputConsumedName; 59 | this.headerList = headerList; 60 | this.instanceStartTime = instanceStartTime; 61 | this.lastEventEndTime = lastEventEndTime; 62 | 63 | } 64 | 65 | public IOEventRecordInfo(String id, String workFlowName, String outputConsumedName, List
headerList, 66 | StopWatch watch, Long instanceStartTime, Long startTime, String lastEventEndTime, String body, 67 | String messageKey,String taskType) { 68 | super(); 69 | this.id = id; 70 | this.workFlowName = workFlowName; 71 | this.outputConsumedName = outputConsumedName; 72 | this.headerList = headerList; 73 | this.watch = watch; 74 | this.instanceStartTime = instanceStartTime; 75 | this.startTime = startTime; 76 | this.lastEventEndTime = lastEventEndTime; 77 | this.body = body; 78 | this.MessageKey = messageKey; 79 | this.taskType = taskType ; 80 | } 81 | 82 | 83 | 84 | 85 | public String getTaskType() { 86 | return taskType; 87 | } 88 | 89 | public void setTaskType(String taskType) { 90 | this.taskType = taskType; 91 | } 92 | 93 | public String getMessageKey() { 94 | return MessageKey; 95 | } 96 | 97 | public void setMessageKey(String messageKey) { 98 | MessageKey = messageKey; 99 | } 100 | 101 | public String getBody() { 102 | return body; 103 | } 104 | 105 | public void setBody(String body) { 106 | this.body = body; 107 | } 108 | 109 | public String getId() { 110 | return id; 111 | } 112 | 113 | public void setId(String id) { 114 | this.id = id; 115 | } 116 | 117 | public String getWorkFlowName() { 118 | return workFlowName; 119 | } 120 | 121 | public void setWorkFlowName(String workFlowName) { 122 | this.workFlowName = workFlowName; 123 | } 124 | 125 | public String getOutputConsumedName() { 126 | return outputConsumedName; 127 | } 128 | 129 | public void setOutputConsumedName(String outputConsumedName) { 130 | this.outputConsumedName = outputConsumedName; 131 | } 132 | 133 | public List
getHeaderList() { 134 | return headerList; 135 | } 136 | 137 | public void setHeaderList(List
headerList) { 138 | this.headerList = headerList; 139 | } 140 | 141 | public StopWatch getWatch() { 142 | return watch; 143 | } 144 | 145 | public void setWatch(StopWatch watch) { 146 | this.watch = watch; 147 | } 148 | 149 | public Long getInstanceStartTime() { 150 | return instanceStartTime; 151 | } 152 | 153 | public void setInstanceStartTime(Long instanceStartTime) { 154 | this.instanceStartTime = instanceStartTime; 155 | } 156 | 157 | public Long getStartTime() { 158 | return startTime; 159 | } 160 | 161 | public void setStartTime(Long startTime) { 162 | this.startTime = startTime; 163 | } 164 | 165 | public String getLastEventEndTime() { 166 | return lastEventEndTime; 167 | } 168 | 169 | public void setLastEventEndTime(String lastEventEndTime) { 170 | this.lastEventEndTime = lastEventEndTime; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/domain/IOEventParallelEventInformation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.domain; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import java.util.ArrayList; 28 | import java.util.HashMap; 29 | import java.util.List; 30 | import java.util.Map; 31 | 32 | import org.apache.kafka.clients.consumer.ConsumerRecord; 33 | 34 | import com.ioevent.starter.configuration.postprocessor.BeanMethodPair; 35 | import com.ioevent.starter.handler.IOEventRecordInfo; 36 | 37 | /** 38 | * this class has information about parallel event : - ClassName for the class 39 | * name which include the @IOEvent annotation, - MethodName for the method name 40 | * which annotated by the @IOEvent annotation, - InputRequired for the Input 41 | * event required to validate the parallel event , - inputsArrived for the 42 | * input event arrived, - listenerTopic for topic name which the listener is 43 | * subscribed, - headers for the header's info sent by events 44 | */ 45 | public class IOEventParallelEventInformation { 46 | 47 | private String value; 48 | private List inputsArrived = new ArrayList<>(); 49 | private Map payloadMap = new HashMap<>(); 50 | private String listenerTopic; 51 | private String method; 52 | private String className; 53 | private List inputRequired; 54 | private Map headers = new HashMap<>(); 55 | 56 | public IOEventParallelEventInformation() { 57 | super(); 58 | } 59 | 60 | public IOEventParallelEventInformation(String value, List inputsArrived, Map payloadMap, 61 | String listenerTopic, String method, String className, List inputRequired, 62 | Map headers) { 63 | super(); 64 | this.value = value; 65 | this.inputsArrived = inputsArrived; 66 | this.payloadMap = payloadMap; 67 | this.listenerTopic = listenerTopic; 68 | this.method = method; 69 | this.className = className; 70 | this.inputRequired = inputRequired; 71 | this.headers = headers; 72 | } 73 | 74 | public IOEventParallelEventInformation(ConsumerRecord consumerRecord, 75 | IOEventRecordInfo ioeventRecordInfo, BeanMethodPair pair, List inputRequired, String appName) { 76 | super(); 77 | this.value = consumerRecord.value(); 78 | this.payloadMap.put(ioeventRecordInfo.getOutputConsumedName(), consumerRecord.value()); 79 | this.inputsArrived.add(ioeventRecordInfo.getOutputConsumedName()); 80 | this.listenerTopic = consumerRecord.topic(); 81 | this.method = pair.getMethod().getName(); 82 | this.className = pair.getBean().getClass().getName(); 83 | this.inputRequired = inputRequired; 84 | headers.put("AppName", appName); 85 | consumerRecord.headers().forEach(header -> this.headers.put(header.key(), new String(header.value()))); 86 | } 87 | 88 | 89 | 90 | public String getValue() { 91 | return value; 92 | } 93 | 94 | public void setValue(String value) { 95 | this.value = value; 96 | } 97 | 98 | public List getInputsArrived() { 99 | return inputsArrived; 100 | } 101 | 102 | public void setInputsArrived(List inputsArrived) { 103 | this.inputsArrived = inputsArrived; 104 | } 105 | 106 | public Map getPayloadMap() { 107 | return payloadMap; 108 | } 109 | 110 | public void setPayloadMap(Map payloadMap) { 111 | this.payloadMap = payloadMap; 112 | } 113 | 114 | public String getListenerTopic() { 115 | return listenerTopic; 116 | } 117 | 118 | public void setListenerTopic(String listenerTopic) { 119 | this.listenerTopic = listenerTopic; 120 | } 121 | 122 | public String getMethod() { 123 | return method; 124 | } 125 | 126 | public void setMethod(String method) { 127 | this.method = method; 128 | } 129 | 130 | public String getClassName() { 131 | return className; 132 | } 133 | 134 | public void setClassName(String className) { 135 | this.className = className; 136 | } 137 | 138 | public List getInputRequired() { 139 | return inputRequired; 140 | } 141 | 142 | public void setInputRequired(List inputRequired) { 143 | this.inputRequired = inputRequired; 144 | } 145 | 146 | public Map getHeaders() { 147 | return headers; 148 | } 149 | 150 | public void setHeaders(Map headers) { 151 | this.headers = headers; 152 | } 153 | 154 | @Override 155 | public String toString() { 156 | return "IOEventParallelEventInformation [value=" + value + ", inputsArrived=" + inputsArrived 157 | + ", listenerTopic=" + listenerTopic + ", method=" + method + ", className=" + className 158 | + ", inputRequired=" + inputRequired + ", headers=" + headers + "]"; 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![Logo](https://www.codeonce.fr/assets/img/codeonce/icon.png) 3 | 4 | #### _IOEvent, an open source microservice framework, created for developers and enterprises for Event-Driven microservices communication and visualization based on the choreography saga._ 5 | --- 6 | [IOEvent](https://www.ioevent.io/) 7 | 8 | --- 9 | 10 | ## What's IOEvent ? 11 | --- 12 | 13 | IOEvent is essentially a new framework for choreographing microservices. It aims to help microservices applications to link the microservices that make up the complete application with simple and easy lines of code without going further into the configuration of the "brorker" technology and without using an orchestrator like the central workflow engines. 14 | 15 | This Framework ensures the transfer of information and communication asynchronously without waiting for a response or worrying about what happens next. Each service observes its environment. Developers will be able to connect its microservices in a short time ensuring the performance of its application thanks to the fast transfer of information between microservices, in addition allowing it to set up a monitoring of execution by process and a "dashboard" of monitoring of execution in BPM. 16 | 17 | 18 | ## Features 19 | --- 20 | 21 | 👩‍💻 **Linking Microservices -** IOEvent allows to link between microservices using simple code that defines the Input and Output of each event by attaching it to any object type. 22 | 23 | ⚡️ **Simple Configurations -** IOEvent provides a default framework configuration for the technologies used by the framework, as well as simple configuration options to customize the configuration of my application. 24 | 25 | 🧠 **Execution Tracking -** IOEvent allows to track the process execution information (number of instances, time spent per instance in a microservice). 26 | 27 | 💬 **Process Supervising -** IOEvent provides a dashboard to display the process diagram created by the microservices with the link created between the microservices and display the current instances in each microservice with all the information about them. 28 | 29 | 30 | 31 | 32 | ## Getting started 33 | --- 34 | You can start using IOEvent by following this steps: 35 | 36 | **Step 0 : System Requirements** 37 | 38 | IOEvent is developed by Spring Boot 2.6.3, it requires Java 11 and Apache Kafka 2.8.0 . 39 | 40 | 41 | **Step 1 : Start with Kafka** 42 | 43 | To start an Apache Kafka server, you can use this docker-compose.yml file : 44 | 45 | 46 | --- 47 | 48 | 49 | version: '3.3' 50 | 51 | volumes: 52 | zookeeper-data: 53 | driver: local 54 | zookeeper-log: 55 | driver: local 56 | kafka-data: 57 | driver: local 58 | 59 | services: 60 | 61 | zookeeper: 62 | restart: always 63 | image: confluentinc/cp-zookeeper 64 | volumes: 65 | - zookeeper-data:/var/lib/zookeeper/data:Z 66 | - zookeeper-log:/var/lib/zookeeper/log:Z 67 | environment: 68 | ZOOKEEPER_CLIENT_PORT: '2181' 69 | ZOOKEEPER_ADMIN_ENABLE_SERVER: 'false' 70 | 71 | kafka: 72 | restart: always 73 | image: confluentinc/cp-kafka:6.2.1 74 | container_name: kafka 75 | volumes: 76 | - kafka-data:/var/lib/kafka:Z 77 | ports: 78 | - "9092:9092" 79 | - "29092:29092" 80 | environment: 81 | KAFKA_BROKER_ID: '0' 82 | KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181' 83 | KAFKA_NUM_PARTITIONS: '12' 84 | KAFKA_COMPRESSION_TYPE: 'gzip' 85 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: '1' 86 | KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: '1' 87 | KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: '1' 88 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT 89 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092 90 | KAFKA_CONFLUENT_SUPPORT_METRICS_ENABLE: 'false' 91 | KAFKA_JMX_PORT: '9091' 92 | KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true' 93 | KAFKA_AUTHORIZER_CLASS_NAME: 'kafka.security.auth.SimpleAclAuthorizer' 94 | KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: 'true' 95 | links: 96 | - zookeeper 97 | 98 | 99 | --- 100 | 101 | 102 | Let’s start the Kafka server by spinning up the containers using the docker-compose command : 103 | 104 | --- 105 | 106 | $ docker-compose up -d 107 | 108 | --- 109 | 110 | 111 | **Step 2 : Add IOEvent Dependency** 112 | 113 | To add IOEvent dependency, edit your pom.xml and add the ioevent-spring-boot-starter : 114 | 115 | --- 116 | 117 | 118 | io.ioevent 119 | ioevent-spring-boot-starter 120 | 1.0.0 121 | 122 | 123 | --- 124 | 125 | **Step 3 : Add IOEvent Property** 126 | 127 | To import IOEvent properties, you can add the following to your application.properties or application.yaml file: 128 | 129 | --- 130 | 131 | spring: 132 | application: 133 | name: "Workflow_Name" 134 | kafka: 135 | bootstrap-servers: localhost:29092 136 | ioevent: 137 | prefix: "Workflow_Prefix" 138 | group_id: "GROUP ID" 139 | api_key: "API_Key" 140 | 141 | 142 | 143 | --- 144 | 145 | **Step 4 : Add @EnableIOEvent** 146 | 147 | --- 148 | 149 | @SpringBootApplication 150 | @EnableIOEvent 151 | public class MyApplication { 152 | 153 | public static void main(String[] args) { 154 | SpringApplication.run(MyApplication.class, args); 155 | } 156 | } 157 | 158 | 159 | 160 | --- 161 | 162 | Now you can start implementing IOEvent methods!! 🛠 😄 163 | 164 | 165 | 166 | ## Documentation 167 | --- 168 | All documentation can be found on [IOEvent](https://doc.ioevent.io/) 📚 169 | 170 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/logger/EventLogger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.logger; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import java.text.DateFormat; 28 | import java.text.ParseException; 29 | import java.text.SimpleDateFormat; 30 | import java.util.Date; 31 | import java.util.TimeZone; 32 | 33 | import org.springframework.kafka.support.KafkaNull; 34 | 35 | import com.fasterxml.jackson.annotation.JsonInclude; 36 | 37 | /**class event logger used to log ioevent Annotation aspect */ 38 | @JsonInclude(JsonInclude.Include.NON_NULL) 39 | public class EventLogger { 40 | private String correlationId; 41 | private String ioflow; 42 | private String stepName; 43 | private String inputEvent; 44 | private String outputEvent; 45 | private String eventType; 46 | private Object payload; 47 | private String startTime; 48 | private String endTime; 49 | private Long duration; 50 | private String errorType; 51 | public EventLogger() { 52 | super(); 53 | } 54 | 55 | public EventLogger(String correlationId, String ioflow, String stepName, String inputEvent, String outputEvent, 56 | String eventType, Object payload) { 57 | this.correlationId = correlationId; 58 | this.ioflow = ioflow; 59 | this.stepName = stepName; 60 | this.inputEvent = inputEvent; 61 | this.outputEvent = outputEvent; 62 | this.eventType = eventType; 63 | this.payload = payload; 64 | } 65 | 66 | public EventLogger(String correlationId, String ioflow, String stepName, String inputEvent, String outputEvent, 67 | String eventType, Object payload, String startTime, String endTime, Long duration) { 68 | this.correlationId = correlationId; 69 | this.ioflow = ioflow; 70 | this.stepName = stepName; 71 | this.inputEvent = inputEvent; 72 | this.outputEvent = outputEvent; 73 | this.eventType = eventType; 74 | this.payload = payload; 75 | this.startTime = startTime; 76 | this.endTime = endTime; 77 | this.duration = duration; 78 | } 79 | 80 | public String getCorrelationId() { 81 | return correlationId; 82 | } 83 | 84 | public void setCorrelationId(String correlationId) { 85 | this.correlationId = correlationId; 86 | } 87 | 88 | public String getIoflow() { 89 | return ioflow; 90 | } 91 | 92 | public void setIoflow(String ioflow) { 93 | this.ioflow = ioflow; 94 | } 95 | 96 | public String getStepName() { 97 | return stepName; 98 | } 99 | 100 | public void setStepName(String stepName) { 101 | this.stepName = stepName; 102 | } 103 | 104 | public String getInputEvent() { 105 | return inputEvent; 106 | } 107 | 108 | public void setInputEvent(String inputEvent) { 109 | this.inputEvent = inputEvent; 110 | } 111 | 112 | public String getOutputEvent() { 113 | return outputEvent; 114 | } 115 | 116 | public void setOutputEvent(String outputEvent) { 117 | this.outputEvent = outputEvent; 118 | } 119 | 120 | public String getEventType() { 121 | return eventType; 122 | } 123 | 124 | public void setEventType(String eventType) { 125 | this.eventType = eventType; 126 | } 127 | 128 | public Object getPayload() { 129 | return payload; 130 | } 131 | 132 | public void setPayload(Object payload) { 133 | this.payload = payload; 134 | } 135 | 136 | public String getStartTime() { 137 | return startTime; 138 | } 139 | 140 | public void setStartTime(String startTime) { 141 | this.startTime = startTime; 142 | } 143 | 144 | public String getEndTime() { 145 | return endTime; 146 | } 147 | 148 | public void setEndTime(String endTime) { 149 | this.endTime = endTime; 150 | } 151 | 152 | public void setErrorType(String errorType) { 153 | this.errorType=errorType; 154 | } 155 | 156 | public String getErrorType() { 157 | return errorType; 158 | } 159 | 160 | public Long getDuration() { 161 | return duration; 162 | } 163 | 164 | public void setDuration(Long duration) { 165 | this.duration = duration; 166 | } 167 | 168 | public void startEventLog() { 169 | this.startTime = this.getISODate(new Date()); 170 | } 171 | 172 | 173 | public void stopEvent() throws ParseException { 174 | this.duration = getTimestamp(this.getEndTime())-getTimestamp(this.getStartTime()); 175 | } 176 | public String getISODate(Date date) { 177 | DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSSS'Z'"); 178 | dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 179 | return dateFormat.format(date); 180 | } 181 | public Long getTimestamp(String stringDate) throws ParseException 182 | { 183 | DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSSS'Z'"); 184 | dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 185 | Date date = dateFormat.parse(stringDate); 186 | 187 | return date.getTime(); 188 | } 189 | public void loggerSetting(String id, String ioflow, String stepName, String string, String outputEvent, 190 | String eventType, Object payload) { 191 | this.correlationId = id; 192 | this.ioflow = ioflow; 193 | this.stepName=stepName; 194 | this.inputEvent=string; 195 | this.outputEvent=outputEvent; 196 | this.eventType=eventType; 197 | if (payload.equals(KafkaNull.INSTANCE)) { 198 | this.payload=null; 199 | } 200 | else { 201 | this.payload=payload; 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/listener/IOEventParrallelListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.listener; 18 | 19 | import java.lang.reflect.Method; 20 | import java.util.List; 21 | import java.util.Optional; 22 | 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.kafka.annotation.KafkaListener; 25 | import org.springframework.stereotype.Service; 26 | import org.springframework.util.StopWatch; 27 | 28 | import com.fasterxml.jackson.core.JsonProcessingException; 29 | import com.fasterxml.jackson.databind.ObjectMapper; 30 | import com.google.gson.Gson; 31 | import com.ioevent.starter.configuration.context.AppContext; 32 | import com.ioevent.starter.configuration.postprocessor.BeanMethodPair; 33 | import com.ioevent.starter.domain.IOEventHeaders; 34 | import com.ioevent.starter.domain.IOEventParallelEventInformation; 35 | import com.ioevent.starter.handler.IOEventRecordInfo; 36 | import com.ioevent.starter.handler.RecordsHandler; 37 | import com.ioevent.starter.service.IOEventContextHolder; 38 | import com.ioevent.starter.service.IOEventService; 39 | 40 | import lombok.extern.slf4j.Slf4j; 41 | 42 | @Slf4j 43 | @Service 44 | public class IOEventParrallelListener { 45 | 46 | ObjectMapper mapper = new ObjectMapper(); 47 | 48 | @Autowired 49 | private List listeners; 50 | @Autowired 51 | RecordsHandler recordsHandler; 52 | @Autowired 53 | private AppContext ctx; 54 | 55 | @Autowired 56 | private IOEventService ioEventService; 57 | 58 | @KafkaListener(topics = "ioevent-parallel-gateway-aggregation", containerFactory = "userKafkaListenerFactory", groupId = "#{'${spring.kafka.consumer.group-id:${ioevent.group_id:${spring.application.name:ioevent_default_groupid}}}'}") 59 | public void consumeParallelEvent(String s) 60 | throws JsonProcessingException, ClassNotFoundException, NoSuchMethodException, SecurityException { 61 | Gson gson = new Gson(); 62 | IOEventParallelEventInformation ioeventParallelEventInformation = gson.fromJson(s, 63 | IOEventParallelEventInformation.class); 64 | if (ioeventParallelEventInformation != null && validParallel(ioeventParallelEventInformation)) { 65 | try { 66 | Object beanmObject = ctx.getApplicationContext() 67 | .getBean(Class.forName(ioeventParallelEventInformation.getClassName())); 68 | if (beanmObject != null) { 69 | new Thread(() -> { 70 | StopWatch watch = new StopWatch(); 71 | watch.start((String) ioeventParallelEventInformation.getHeaders() 72 | .get(IOEventHeaders.CORRELATION_ID.toString())); 73 | IOEventRecordInfo ioeventRecordInfo = new IOEventRecordInfo( 74 | ioeventParallelEventInformation.getHeaders().get(IOEventHeaders.CORRELATION_ID.toString()) 75 | .toString(), 76 | ioeventParallelEventInformation.getHeaders().get(IOEventHeaders.PROCESS_NAME.toString()) 77 | .toString(), 78 | ioeventParallelEventInformation.getInputsArrived().toString(), watch, 79 | Long.valueOf(ioeventParallelEventInformation.getHeaders() 80 | .get(IOEventHeaders.START_INSTANCE_TIME.toString()).toString()),null); 81 | IOEventContextHolder.setContext(ioeventRecordInfo); 82 | 83 | try { 84 | invokeTargetMethod(ioeventParallelEventInformation.getMethod(), beanmObject, 85 | ioeventParallelEventInformation); 86 | } catch (Throwable e) { 87 | e.printStackTrace(); 88 | } 89 | }).start(); 90 | } 91 | } catch (Throwable e) { 92 | log.error("error while invoking method "); 93 | } 94 | 95 | } else { 96 | log.info("Parallel Event Input Not Completed, output arrived : " 97 | + ioeventParallelEventInformation.getInputsArrived()); 98 | } 99 | 100 | } 101 | 102 | private boolean validParallel (IOEventParallelEventInformation ioeventParallelEventInformation) 103 | { 104 | return sameList(ioeventParallelEventInformation.getInputRequired(), 105 | ioeventParallelEventInformation.getInputsArrived()); 106 | } 107 | 108 | 109 | /** method to invoke the method from a specific bean **/ 110 | public void invokeTargetMethod(String methodName, Object beanmObject, 111 | IOEventParallelEventInformation parallelEventInformation) throws Throwable { 112 | if (beanmObject != null) { 113 | 114 | for (Method met : beanmObject.getClass().getDeclaredMethods()) { 115 | if (met.getName().equals(methodName)) { 116 | Method method = met; 117 | 118 | for (Listener listener : listeners) { 119 | Optional pair = listener.getBeanMethodPairs().stream().filter( 120 | x -> (x.getBean().getClass().getName().equals(parallelEventInformation.getClassName()) 121 | && x.getMethod().getName().equals(parallelEventInformation.getMethod()))) 122 | .findFirst(); 123 | if (pair.isPresent()) { 124 | method = pair.get().getMethod(); 125 | } 126 | } 127 | Object[] params = recordsHandler.prepareParallelParameters(method, parallelEventInformation); 128 | recordsHandler.invokeWithtwoParameter(method, beanmObject, params); 129 | 130 | } 131 | } 132 | 133 | } 134 | } 135 | 136 | public Object parseConsumedValue(Object consumedValue, Class type) throws JsonProcessingException { 137 | if (type.equals(String.class)) { 138 | return consumedValue; 139 | } else { 140 | return mapper.readValue(consumedValue.toString(), type); 141 | } 142 | } 143 | 144 | public boolean sameList(List firstList, List secondList) { 145 | return (firstList.size() == secondList.size() && firstList.containsAll(secondList) 146 | && secondList.containsAll(firstList)); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/configuration/kafka/KafkaConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.configuration.kafka; 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | import java.util.Map; 29 | 30 | import org.apache.kafka.clients.admin.AdminClient; 31 | import org.apache.kafka.clients.consumer.ConsumerConfig; 32 | import org.apache.kafka.clients.producer.ProducerConfig; 33 | import org.apache.kafka.common.serialization.Serdes; 34 | import org.apache.kafka.common.serialization.StringDeserializer; 35 | import org.apache.kafka.common.serialization.StringSerializer; 36 | import org.apache.kafka.streams.StreamsConfig; 37 | import org.springframework.beans.factory.annotation.Autowired; 38 | import org.springframework.beans.factory.annotation.Value; 39 | import org.springframework.boot.autoconfigure.kafka.KafkaProperties; 40 | import org.springframework.context.annotation.Bean; 41 | import org.springframework.kafka.annotation.KafkaStreamsDefaultConfiguration; 42 | import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; 43 | import org.springframework.kafka.config.KafkaStreamsConfiguration; 44 | import org.springframework.kafka.core.ConsumerFactory; 45 | import org.springframework.kafka.core.DefaultKafkaConsumerFactory; 46 | import org.springframework.kafka.core.DefaultKafkaProducerFactory; 47 | import org.springframework.kafka.core.KafkaTemplate; 48 | import org.springframework.kafka.core.ProducerFactory; 49 | import org.springframework.kafka.support.DefaultKafkaHeaderMapper; 50 | import org.springframework.kafka.support.serializer.JsonSerializer; 51 | 52 | import lombok.extern.slf4j.Slf4j; 53 | 54 | /** 55 | * class that contain the Configuration of the Broker Kafka 56 | **/ 57 | @Slf4j 58 | public class KafkaConfig { 59 | @Autowired 60 | private KafkaProperties kafkaProperties; 61 | @Value("${spring.kafka.state.dir:/tmp/var/lib/kafka-streams-newconfluent8}") 62 | private String stateDir; 63 | 64 | @Value("#{'${spring.kafka.consumer.group-id:${ioevent.group_id:${spring.application.name:ioevent_default_groupid}}}'}") 65 | private String kafkaGroup_id; 66 | @Value("${spring.kafka.streams.replication-factor:1}") 67 | private String topicReplication; 68 | 69 | /** 70 | * Bean to create the kafka admin client configuration, 71 | * 72 | * @return AdminClient Object, 73 | **/ 74 | @Bean 75 | public AdminClient adminClient() { 76 | Map config = kafkaProperties.buildAdminProperties(); 77 | config.put("connections.max.idle.ms", 10000); 78 | config.put("request.timeout.ms", 20000); 79 | config.put("retry.backoff.ms", 500); 80 | 81 | return AdminClient.create(config); 82 | } 83 | 84 | /** 85 | * Bean to define the kafka stream configuration, 86 | * 87 | * @return KafkaStreamsConfiguration Object, 88 | **/ 89 | @Bean(name = KafkaStreamsDefaultConfiguration.DEFAULT_STREAMS_CONFIG_BEAN_NAME) 90 | public KafkaStreamsConfiguration kStreamsConfigs() { 91 | kafkaGroup_id = kafkaGroup_id.replaceAll("\\s+",""); 92 | Map config = kafkaProperties.buildStreamsProperties(); 93 | config.put(StreamsConfig.APPLICATION_ID_CONFIG, kafkaGroup_id + "_Stream"); 94 | config.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName()); 95 | config.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName()); 96 | config.put(StreamsConfig.CACHE_MAX_BYTES_BUFFERING_CONFIG, "0"); 97 | config.put(StreamsConfig.REPLICATION_FACTOR_CONFIG, topicReplication); 98 | config.put(StreamsConfig.STATE_DIR_CONFIG, stateDir); 99 | config.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); 100 | config.put(ProducerConfig.LINGER_MS_CONFIG, 5); 101 | 102 | return new KafkaStreamsConfiguration(config); 103 | } 104 | 105 | /** 106 | * Bean to define the kafka producer configuration, 107 | * 108 | * @return ProducerFactory Object, 109 | **/ 110 | @Bean 111 | public ProducerFactory producerFactory() { 112 | Map config = kafkaProperties.buildProducerProperties(); 113 | config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); 114 | config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); 115 | 116 | return new DefaultKafkaProducerFactory<>(config); 117 | } 118 | 119 | /** 120 | * Bean to init the kafka Template, 121 | * 122 | * @return ProducerFactory Object, 123 | **/ 124 | @Bean 125 | public KafkaTemplate kafkaTemplate() { 126 | return new KafkaTemplate<>(producerFactory()); 127 | } 128 | 129 | /** 130 | * Bean to define the kafka Consumer configuration, 131 | * 132 | * @return ConsumerFactory Object, 133 | **/ 134 | @Bean 135 | public ConsumerFactory userConsumerFactory() { 136 | Map config = kafkaProperties.buildConsumerProperties(); 137 | config.put(ConsumerConfig.GROUP_ID_CONFIG, kafkaGroup_id); 138 | config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); 139 | config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); 140 | config.put(ConsumerConfig.FETCH_MAX_WAIT_MS_CONFIG, 10); 141 | 142 | return new DefaultKafkaConsumerFactory<>(config, new StringDeserializer(), new StringDeserializer()); 143 | } 144 | 145 | @Bean 146 | public DefaultKafkaHeaderMapper headerMapper() { 147 | return new DefaultKafkaHeaderMapper(); 148 | } 149 | 150 | /** 151 | * Bean to define the KafkaListenerContainerFactory, 152 | * 153 | * @return ConcurrentKafkaListenerContainerFactory Object, 154 | **/ 155 | @Bean 156 | public ConcurrentKafkaListenerContainerFactory userKafkaListenerFactory() { 157 | ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); 158 | factory.setConsumerFactory(userConsumerFactory()); 159 | return factory; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/annotations/IOEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.annotations; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | import com.ioevent.starter.enums.EventTypesEnum; 25 | 26 | /** 27 | * Annotation that marks a method of IOEvent, in @IOEvent we can specify the key 28 | * or the name of the task, name of generic topic where the @IOEvent methods 29 | * will receive and send an event if the topic wasn’t specified in 30 | * the @InputEvent and @OutputEvent annotation, it also specify input as list 31 | * of @InputEvent from where the annotation will receive events and output as 32 | * list of @OutputEvent where the annotation will send events, it can define the 33 | * method as Gateway using @GatewayOutputEvent and @GatewayInputEvent, finally 34 | * he can declare start/end method explicitly using @StartEvent and @EndEvent or 35 | * declare start/end method implicitly if you don’t mention any input/output. 36 | **/ 37 | @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) 38 | @Retention(RetentionPolicy.RUNTIME) 39 | public @interface IOEvent { 40 | 41 | /** 42 | * 43 | * The key of ioevent task define the task name 44 | * 45 | * @return the ioevent task key 46 | */ 47 | String key() default ""; 48 | 49 | /** 50 | * The topic name for ioevent task from where to consume events that can invoke 51 | * the method or produce event into it after running this ioevent method 52 | * 53 | * @return the topic name 54 | */ 55 | String topic() default ""; 56 | 57 | /** 58 | * 59 | * Array of @InputEvent ,specify input as array of @InputEvent which create a 60 | * Listener for each input and receive events from the topic ( if the topic is 61 | * not mentioned it will listen to the generic topic specified in the @IOEvent 62 | * or @IFlow annotation ), and while the listener consumes an event it will 63 | * verify if the output key of the received event is equal to the @InputEvent 64 | * key in order to invoke the specific method. 65 | * 66 | * @return Array of @InputEvent 67 | */ 68 | InputEvent[] input() default @InputEvent(); 69 | 70 | /** 71 | * Returns a @GatewayInputEvent used to converge parallel branches, it waits 72 | * until receiving all input branches @InputEvent to execute the ioevent method 73 | * and send the event to the @OutputEvent. 74 | * 75 | * @return a GatewayInputEvent object 76 | */ 77 | GatewayInputEvent gatewayInput() default @GatewayInputEvent(); 78 | 79 | /** 80 | * Array of @OutputEvent annotation is used to produce an event which includes a 81 | * key of the output and a topic where the event will be produced ( if the topic 82 | * is not mentioned the event will be sent to the generic topic specified in 83 | * the @IOEvent or @IFlow annotation ). 84 | * 85 | * @return Array of @OutputEvent 86 | */ 87 | OutputEvent[] output() default @OutputEvent(); 88 | 89 | /** 90 | * Returns a @GatewayOutputEvent used to declare either an exclusive or parallel 91 | * gateway . 92 | *

93 | * In case of exclusive gateway : An exclusive gateway evaluates the state of 94 | * the business process and, based on the returned IOResponse key, it breaks the 95 | * flow into one of the two or more mutually exclusive paths. we set the value 96 | * of exclusive to true and define the list of @OutputEvent where the method 97 | * will produce the event to the output with the same key of the IOResponse 98 | * output key. In IOResponse we specify the output key and the body to be send 99 | * to the event. 100 | *

101 | * In case of Parallel gateway : it models a fork into multiple paths of 102 | * execution ,we set the value of parallel to true and define the list of output 103 | * branches @OutputEvent where to produce the event simultaneously. 104 | * 105 | * @return a GatewayOutputEvent object 106 | */ 107 | 108 | GatewayOutputEvent gatewayOutput() default @GatewayOutputEvent(); 109 | 110 | /** 111 | * A @StartEvent annotation define the starting point of a process which 112 | * includes a key where we specify the name of the flow. 113 | * 114 | * 115 | * @return a StartEvent object 116 | */ 117 | StartEvent startEvent() default @StartEvent(); 118 | 119 | /** 120 | * An @EndEvent annotation define the finishing point of a process which 121 | * includes a key where we specify the name of the flow. 122 | * 123 | * @return an EndEvent object 124 | */ 125 | EndEvent endEvent() default @EndEvent(); 126 | 127 | /** 128 | * An @ExceptionEvent is an annotation that defines error handling in two ways : 129 | *

130 | * We can choose to end the flow with end error, inside the @ExceptionEvent we 131 | * specify the list of exceptions predicted to be throwed by the task and we add 132 | * the @EndEvent which define the end of the flow with an error. 133 | *

134 | * Or Create a boundary event to send the error to the handling task : inside 135 | * the @ExceptionEvent we specify the list of exceptions predicted to be 136 | * occurred in the task and declare the output which is an @OutputEvent where 137 | * the payload would be sent to the handling method associated to the output. 138 | * 139 | * @return an ExceptionEvent object 140 | */ 141 | ExceptionEvent exception() default @ExceptionEvent(); 142 | 143 | /** 144 | * eventType is a specification of the task type. 145 | *ioevent supports service, receive and user tasks. 146 | * 147 | * @return EventTypesEnum object 148 | */ 149 | EventTypesEnum EventType() default EventTypesEnum.SERVICE; 150 | 151 | /** 152 | * Text annotation is a label attached to a bpmn object. 153 | * 154 | * @return String 155 | */ 156 | String textAnnotation() default ""; 157 | 158 | /** 159 | * Timer event to schedule start events for specific period or date. 160 | * 161 | * @return IOTimer Object 162 | */ 163 | IOTimer timer() default @IOTimer(); 164 | 165 | IOMessage message() default @IOMessage(); 166 | 167 | 168 | 169 | } 170 | -------------------------------------------------------------------------------- /src/test/java/com/ioevent/starter/configuration/aspect/v2/IOEventStartAspectTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.configuration.aspect.v2; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import static org.assertj.core.api.Assertions.assertThatNoException; 28 | import static org.junit.jupiter.api.Assertions.assertEquals; 29 | import static org.mockito.Mockito.when; 30 | 31 | import java.lang.reflect.Method; 32 | import java.text.ParseException; 33 | import java.util.ArrayList; 34 | import java.util.Arrays; 35 | import java.util.Date; 36 | import java.util.UUID; 37 | import java.util.concurrent.CompletableFuture; 38 | 39 | import org.aspectj.lang.JoinPoint; 40 | import org.junit.Assert; 41 | import org.junit.jupiter.api.BeforeEach; 42 | import org.junit.jupiter.api.Test; 43 | import org.mockito.InjectMocks; 44 | import org.mockito.Mock; 45 | import org.mockito.Mockito; 46 | import org.mockito.MockitoAnnotations; 47 | import org.springframework.kafka.core.KafkaTemplate; 48 | import org.springframework.kafka.support.KafkaHeaders; 49 | import org.springframework.kafka.support.SendResult; 50 | import org.springframework.messaging.Message; 51 | import org.springframework.messaging.support.MessageBuilder; 52 | import org.springframework.util.StopWatch; 53 | 54 | import com.fasterxml.jackson.core.JsonProcessingException; 55 | import com.ioevent.starter.annotations.IOEvent; 56 | import com.ioevent.starter.annotations.IOResponse; 57 | import com.ioevent.starter.annotations.InputEvent; 58 | import com.ioevent.starter.annotations.OutputEvent; 59 | import com.ioevent.starter.annotations.StartEvent; 60 | import com.ioevent.starter.configuration.properties.IOEventProperties; 61 | import com.ioevent.starter.domain.IOEventHeaders; 62 | import com.ioevent.starter.domain.IOEventType; 63 | import com.ioevent.starter.logger.EventLogger; 64 | import com.ioevent.starter.service.IOEventService; 65 | 66 | class IOEventStartAspectTest { 67 | 68 | @InjectMocks 69 | IOEventStartAspect startAspect = new IOEventStartAspect(); 70 | @Mock 71 | IOEventService ioEventService; 72 | @Mock 73 | IOEventProperties iOEventProperties; 74 | @Mock 75 | JoinPoint joinPoint; 76 | @Mock 77 | KafkaTemplate kafkaTemplate ; 78 | @BeforeEach 79 | public void init() { 80 | 81 | MockitoAnnotations.initMocks(this); 82 | } 83 | 84 | /** method to test annotations **/ 85 | @IOEvent(key = "stepname", startEvent = @StartEvent("process name"), topic = "topic", output = @OutputEvent(key = "output", topic = "topic")) 86 | public boolean startAnnotationMethod() { 87 | return true; 88 | } 89 | 90 | /** method to test annotations **/ 91 | @IOEvent(key = "stepname", topic = "topic", startEvent = @StartEvent(key = "process name"), output = @OutputEvent(key = "output")) 92 | public boolean startAnnotationMethod2() { 93 | return true; 94 | } 95 | /** method to test annotations **/ 96 | @IOEvent(key = "test annotation", topic = "topic1", // 97 | input = @InputEvent(key = "input", topic = "T"), output = @OutputEvent(key = "output", topic = "T")) 98 | public boolean simpleTaskAnnotationMethod() { 99 | return true; 100 | } 101 | @Test 102 | void testTryAnnotationmethod() { 103 | IOEventStartAspectTest serviceTest = Mockito.spy(this); 104 | Assert.assertEquals(true, startAnnotationMethod()); 105 | Assert.assertEquals(true, startAnnotationMethod2()); 106 | Assert.assertEquals(true, simpleTaskAnnotationMethod()); 107 | 108 | } 109 | 110 | @Test 111 | void buildStartMessageTest() throws NoSuchMethodException, SecurityException { 112 | when(iOEventProperties.getPrefix()).thenReturn("test-"); 113 | when(ioEventService.getOutputTopicName(Mockito.any(IOEvent.class), Mockito.any(), Mockito.any(String.class))).thenReturn("topic"); 114 | Method method = this.getClass().getMethod("startAnnotationMethod", null); 115 | IOEvent ioEvent = method.getAnnotation(IOEvent.class); 116 | when(ioEventService.getIOEventType(ioEvent)).thenReturn(IOEventType.START); 117 | IOResponse ioEventResponse = new IOResponse<>(null, "payload", null); 118 | Message messageResult = startAspect.buildStartMessage(ioEvent, null,ioEventResponse,"process", "1155", ioEvent.output()[0], 119 | (long) 123546, "example"); 120 | Message message = MessageBuilder.withPayload("payload").setHeader(KafkaHeaders.TOPIC, "test-topic") 121 | .setHeader(KafkaHeaders.KEY, "1155").setHeader(IOEventHeaders.CORRELATION_ID.toString(), "1155") 122 | .setHeader("IOEventHeaders.STEP_NAME.toString()", "stepname").setHeader(IOEventHeaders.EVENT_TYPE.toString(), IOEventType.START.toString()) 123 | .setHeader(IOEventHeaders.INPUT.toString(), new ArrayList(Arrays.asList("Start"))).setHeader(IOEventHeaders.OUTPUT_EVENT.toString(), "output") 124 | .setHeader(IOEventHeaders.PROCESS_NAME.toString(), "process name").setHeader(IOEventHeaders.START_TIME.toString(), (long) 123546).build(); 125 | assertEquals(message.getHeaders().get("kafka_messageKey"), messageResult.getHeaders().get("kafka_messageKey")); 126 | 127 | Method method2 = this.getClass().getMethod("startAnnotationMethod2", null); 128 | IOEvent ioEvent2 = method2.getAnnotation(IOEvent.class); 129 | when(ioEventService.getIOEventType(ioEvent2)).thenReturn(IOEventType.START); 130 | 131 | Message messageResult2 = startAspect.buildStartMessage(ioEvent2,null, ioEventResponse,"process", "1155", ioEvent2.output()[0], 132 | (long) 123546, "example"); 133 | assertEquals(message.getHeaders().get("kafka_topic"), messageResult2.getHeaders().get("kafka_topic")); 134 | 135 | } 136 | 137 | @Test 138 | void prepareAndDisplayEventLoggerTest() throws JsonProcessingException, NoSuchMethodException, SecurityException, ParseException { 139 | 140 | when(joinPoint.getArgs()).thenReturn(new String[] { "payload" }); 141 | Method method = this.getClass().getMethod("startAnnotationMethod", null); 142 | IOEvent ioEvent = method.getAnnotation(IOEvent.class); 143 | UUID uuid = UUID.randomUUID(); 144 | StopWatch watch = new StopWatch(); 145 | EventLogger eventLogger = new EventLogger(); 146 | eventLogger.startEventLog(); 147 | eventLogger.setEndTime(eventLogger.getISODate(new Date())); 148 | 149 | watch.start("IOEvent annotation Start Aspect"); 150 | startAspect.prepareAndDisplayEventLogger(eventLogger, uuid, ioEvent, "process","output", "payload", watch); 151 | 152 | assertThatNoException(); 153 | 154 | } 155 | 156 | @Test 157 | void iOEventAnnotationAspectTest() throws Throwable { 158 | Method method = this.getClass().getMethod("startAnnotationMethod", null); 159 | IOEvent ioEvent = method.getAnnotation(IOEvent.class); 160 | Method method2 = this.getClass().getMethod("simpleTaskAnnotationMethod", null); 161 | IOEvent ioEvent2 = method2.getAnnotation(IOEvent.class); 162 | CompletableFuture> future = new CompletableFuture<>(); 163 | when(kafkaTemplate.send(Mockito.any(Message.class))).thenReturn(future); 164 | when(ioEventService.getOutputs(ioEvent)).thenReturn(Arrays.asList(ioEvent.output())); 165 | when(iOEventProperties.getPrefix()).thenReturn("test-"); 166 | when(joinPoint.getArgs()).thenReturn(new String[] { "payload" }); 167 | 168 | startAspect.iOEventAnnotationAspect(joinPoint, ioEvent,null); 169 | startAspect.iOEventAnnotationAspect(joinPoint, ioEvent2,null); 170 | 171 | 172 | assertThatNoException(); 173 | 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/test/java/com/ioevent/starter/configuration/postprocessor/IOEventBpmnPostProcessorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.configuration.postprocessor; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import static org.junit.Assert.assertEquals; 28 | import static org.junit.Assert.assertFalse; 29 | import static org.junit.Assert.assertTrue; 30 | import static org.mockito.Mockito.when; 31 | 32 | import java.lang.reflect.Method; 33 | import java.util.ArrayList; 34 | import java.util.List; 35 | import java.util.UUID; 36 | 37 | import org.junit.Assert; 38 | import org.junit.jupiter.api.BeforeEach; 39 | import org.junit.jupiter.api.Test; 40 | import org.mockito.InjectMocks; 41 | import org.mockito.Mock; 42 | import org.mockito.Mockito; 43 | import org.mockito.MockitoAnnotations; 44 | import org.mockito.Spy; 45 | import org.springframework.beans.factory.annotation.Value; 46 | 47 | import com.ioevent.starter.annotations.EndEvent; 48 | import com.ioevent.starter.annotations.IOEvent; 49 | import com.ioevent.starter.annotations.InputEvent; 50 | import com.ioevent.starter.annotations.OutputEvent; 51 | import com.ioevent.starter.annotations.StartEvent; 52 | import com.ioevent.starter.configuration.properties.IOEventProperties; 53 | import com.ioevent.starter.domain.IOEventBpmnPart; 54 | import com.ioevent.starter.domain.IOEventType; 55 | import com.ioevent.starter.enums.EventTypesEnum; 56 | import com.ioevent.starter.listener.Listener; 57 | import com.ioevent.starter.service.IOEventService; 58 | import org.springframework.boot.test.mock.mockito.MockBean; 59 | import org.springframework.boot.test.mock.mockito.SpyBean; 60 | 61 | class IOEventBpmnPostProcessorTest { 62 | @Value("${spring.application.name}") 63 | private String appName; 64 | @InjectMocks 65 | IOEventBpmnPostProcessor ioeventBpmnPostProcessor = new IOEventBpmnPostProcessor(); 66 | @Mock 67 | IOEventService ioEventService; 68 | @Mock 69 | IOEventProperties iOEventProperties; 70 | @BeforeEach 71 | public void init() { 72 | 73 | MockitoAnnotations.initMocks(this); 74 | } 75 | 76 | /** method to test annotations **/ 77 | @IOEvent(key = "test annotation", topic = "topic1", // 78 | input = @InputEvent(key = "input", topic = "T"), output = @OutputEvent(key = "output", topic = "T")) 79 | public boolean simpleTaskAnnotationMethod() { 80 | return true; 81 | } 82 | 83 | /** method to test annotations **/ 84 | @IOEvent(key = "test annotation", topic = "topic1"// 85 | , startEvent = @StartEvent(key = "startkey")) // // 86 | public boolean startAnnotationMethod() { 87 | return true; 88 | } 89 | 90 | /** method to test annotations **/ 91 | @IOEvent(key = "test annotation", topic = "topic1", endEvent = @EndEvent(key = "endkey")) 92 | public boolean endAnnotationMethod() { 93 | return true; 94 | } 95 | 96 | @Test 97 | void testTryAnnotationmethod() { 98 | IOEventBpmnPostProcessorTest serviceTest = Mockito.spy(this); 99 | Assert.assertEquals(true, simpleTaskAnnotationMethod()); 100 | Assert.assertEquals(true, startAnnotationMethod()); 101 | Assert.assertEquals(true, endAnnotationMethod()); 102 | 103 | } 104 | 105 | @Test 106 | void Create_Start_ioEventBpmnPart() throws NoSuchMethodException, SecurityException { 107 | Method startMethod = this.getClass().getMethod("startAnnotationMethod", null); 108 | IOEvent ioEventStart = startMethod.getAnnotation(IOEvent.class); 109 | UUID bpmnPartId = UUID.randomUUID(); 110 | when(ioEventService.getIOEventType(ioEventStart)).thenReturn(IOEventType.START); 111 | when(ioEventService.getProcessName(ioEventStart,null,"")).thenReturn("startkey"); 112 | when(iOEventProperties.getApikey()).thenReturn(""); 113 | when(ioEventService.getApiKey(iOEventProperties, null)).thenReturn(""); 114 | IOEventBpmnPart ioEventBpmnPartCreated = ioeventBpmnPostProcessor.createIOEventBpmnPart(ioEventStart,null,null, "testClass", 115 | bpmnPartId.toString(), "testMethod","type"); 116 | IOEventBpmnPart ioEventBpmnPart = new IOEventBpmnPart(ioEventStart,null, bpmnPartId.toString(),"",appName, "startkey", IOEventType.START, 117 | "test annotation", "testMethod","type","",EventTypesEnum.SERVICE,""); 118 | 119 | assertEquals(ioEventBpmnPart.getWorkflow(), ioEventBpmnPartCreated.getWorkflow()); 120 | 121 | } 122 | 123 | @Test 124 | void Create_End_ioEventBpmnPart() throws NoSuchMethodException, SecurityException { 125 | 126 | Method endMethod = this.getClass().getMethod("endAnnotationMethod", null); 127 | IOEvent ioEventEnd = endMethod.getAnnotation(IOEvent.class); 128 | UUID bpmnPartId = UUID.randomUUID(); 129 | when(ioEventService.getIOEventType(ioEventEnd)).thenReturn(IOEventType.END); 130 | when(ioEventService.getProcessName(ioEventEnd,null,"")).thenReturn("endkey"); 131 | when(iOEventProperties.getApikey()).thenReturn(""); 132 | when(ioEventService.getApiKey(iOEventProperties, null)).thenReturn(""); 133 | IOEventBpmnPart ioEventBpmnPartCreated = ioeventBpmnPostProcessor.createIOEventBpmnPart(ioEventEnd,null,null, "testClass", 134 | bpmnPartId.toString(), "type","testMethod"); 135 | IOEventBpmnPart ioEventBpmnPart = new IOEventBpmnPart(ioEventEnd,null, bpmnPartId.toString(),"",appName, "endkey", IOEventType.END, 136 | "test annotation", "testMethod","type","",EventTypesEnum.SERVICE,""); 137 | 138 | assertEquals(ioEventBpmnPart.getWorkflow(), ioEventBpmnPartCreated.getWorkflow()); 139 | 140 | } 141 | 142 | @Test 143 | void Create_Task_ioEventBpmnPart() throws NoSuchMethodException, SecurityException { 144 | Method taskMethod = this.getClass().getMethod("simpleTaskAnnotationMethod", null); 145 | IOEvent ioEventTask = taskMethod.getAnnotation(IOEvent.class); 146 | UUID bpmnPartId = UUID.randomUUID(); 147 | when(ioEventService.getIOEventType(ioEventTask)).thenReturn(IOEventType.TASK); 148 | when(ioEventService.getProcessName(ioEventTask,null,"")).thenReturn(""); 149 | when(iOEventProperties.getApikey()).thenReturn(""); 150 | when(ioEventService.getApiKey(iOEventProperties, null)).thenReturn(""); 151 | IOEventBpmnPart ioEventBpmnPartCreated = ioeventBpmnPostProcessor.createIOEventBpmnPart(ioEventTask,null,null, "testClass", 152 | bpmnPartId.toString(),"type", "testMethod"); 153 | IOEventBpmnPart ioEventBpmnPart = new IOEventBpmnPart(ioEventTask,null, bpmnPartId.toString(),"",appName, "", IOEventType.TASK, 154 | "test annotation", "testMethod","type","",EventTypesEnum.SERVICE,"test"); 155 | 156 | assertEquals(ioEventBpmnPart.getWorkflow(), ioEventBpmnPartCreated.getWorkflow()); 157 | 158 | } 159 | //@Spy 160 | List listeners =new ArrayList(); 161 | // new Listener(null, null, null, null, null, "Topic"); 162 | 163 | @Test 164 | void ListenerExist_returnTrue() throws InterruptedException, NoSuchMethodException, SecurityException { 165 | Method taskMethod = this.getClass().getMethod("simpleTaskAnnotationMethod", null); 166 | when(iOEventProperties.getPrefix()).thenReturn("test_"); 167 | listeners.add(new Listener(null, null, this, taskMethod, null, "test_Topic")); 168 | ioeventBpmnPostProcessor.setListeners(listeners); 169 | assertTrue(ioeventBpmnPostProcessor.listenerExist("Topic", this, taskMethod, null)); 170 | } 171 | @Test 172 | void ListenerExist_returnFalse() throws InterruptedException { 173 | ioeventBpmnPostProcessor.setListeners(listeners); 174 | assertFalse(ioeventBpmnPostProcessor.listenerExist("Topic", null, null, null)); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/test/java/com/ioevent/starter/handler/RecordsHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.handler; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import static org.assertj.core.api.Assertions.assertThatNoException; 28 | import static org.junit.Assert.assertEquals; 29 | import static org.mockito.Mockito.when; 30 | 31 | import java.lang.reflect.Method; 32 | import java.util.Arrays; 33 | import java.util.List; 34 | import java.util.concurrent.CompletableFuture; 35 | 36 | import org.apache.kafka.clients.consumer.ConsumerRecord; 37 | import org.apache.kafka.common.header.internals.RecordHeaders; 38 | import org.apache.kafka.common.record.TimestampType; 39 | import org.junit.jupiter.api.BeforeEach; 40 | import org.junit.jupiter.api.Test; 41 | import org.junit.runner.RunWith; 42 | import org.mockito.InjectMocks; 43 | import org.mockito.Mock; 44 | import org.mockito.Mockito; 45 | import org.mockito.MockitoAnnotations; 46 | import org.springframework.kafka.core.KafkaTemplate; 47 | import org.springframework.kafka.support.KafkaHeaders; 48 | import org.springframework.kafka.support.SendResult; 49 | import org.springframework.messaging.Message; 50 | import org.springframework.messaging.support.MessageBuilder; 51 | import org.springframework.test.context.junit4.SpringRunner; 52 | import org.springframework.util.StopWatch; 53 | 54 | import com.fasterxml.jackson.core.JsonProcessingException; 55 | import com.fasterxml.jackson.databind.JsonMappingException; 56 | import com.google.gson.Gson; 57 | import com.ioevent.starter.configuration.postprocessor.BeanMethodPair; 58 | import com.ioevent.starter.domain.IOEventHeaders; 59 | import com.ioevent.starter.domain.IOEventParallelEventInformation; 60 | import com.ioevent.starter.domain.ParallelEventInfo; 61 | import com.ioevent.starter.service.IOEventService; 62 | 63 | @RunWith(SpringRunner.class) 64 | class RecordsHandlerTest { 65 | 66 | @InjectMocks 67 | RecordsHandler recordsHandler = new RecordsHandler(); 68 | 69 | @Mock 70 | IOEventService ioEventService; 71 | 72 | @Mock 73 | KafkaTemplate kafkaTemplate; 74 | 75 | @BeforeEach 76 | public void init() { 77 | MockitoAnnotations.initMocks(this); 78 | } 79 | 80 | Gson gson = new Gson(); 81 | 82 | @Test 83 | void parseConsumedValueTest() throws JsonMappingException, JsonProcessingException { 84 | // test String object 85 | Object string = "test String"; 86 | assertEquals(String.class, recordsHandler.parseConsumedValue(string, String.class).getClass()); 87 | // test custom object 88 | ParallelEventInfo parallelEventInfo = new ParallelEventInfo("id", 89 | Arrays.asList("first element ", "second element")); 90 | Object parallelString = gson.toJson(parallelEventInfo); 91 | assertEquals(ParallelEventInfo.class, 92 | recordsHandler.parseConsumedValue(parallelString, ParallelEventInfo.class).getClass()); 93 | 94 | } 95 | 96 | @Test 97 | void parseStringToArrayTest() { 98 | 99 | String listInString = " one, two, three,"; 100 | List listOfStrings = Arrays.asList("one", "two", "three"); 101 | List resultList = recordsHandler.parseStringToArray(listInString); 102 | 103 | assertEquals(listOfStrings, resultList); 104 | } 105 | 106 | @Test 107 | void getIOEventHeadersTest() { 108 | ConsumerRecord consumerRecord = new ConsumerRecord("topic", 1, 152, 11125, 109 | TimestampType.LOG_APPEND_TIME, null, 0, 0, null, null, new RecordHeaders()); 110 | consumerRecord.headers().add(IOEventHeaders.OUTPUT_EVENT.toString(), "output name".getBytes()); 111 | consumerRecord.headers().add(IOEventHeaders.CORRELATION_ID.toString(), "id".getBytes()); 112 | consumerRecord.headers().add(IOEventHeaders.PROCESS_NAME.toString(), "workflow name".getBytes()); 113 | consumerRecord.headers().add("another header", "value".getBytes()); 114 | 115 | IOEventRecordInfo ioeventRecordInfoCreated = recordsHandler.getIOEventHeaders(consumerRecord); 116 | IOEventRecordInfo ioeventRecordInfo = new IOEventRecordInfo("id", "workflow name", "output name", new StopWatch(),1000L,null); 117 | assertEquals(ioeventRecordInfo.getId(), ioeventRecordInfoCreated.getId()); 118 | assertEquals(ioeventRecordInfo.getOutputConsumedName(), ioeventRecordInfoCreated.getOutputConsumedName()); 119 | assertEquals(ioeventRecordInfo.getWorkFlowName(), ioeventRecordInfoCreated.getWorkFlowName()); 120 | 121 | } 122 | 123 | @Test 124 | void sendParallelInfoTest() throws NoSuchMethodException, SecurityException { 125 | Method method = this.getClass().getMethod("init", null); 126 | CompletableFuture> future = new CompletableFuture<>(); 127 | when(kafkaTemplate.send(Mockito.any(Message.class))).thenReturn(future); 128 | IOEventRecordInfo ioeventRecordInfo = new IOEventRecordInfo("1155", "process name", "Output 1", new StopWatch(),1000L,null); 129 | ConsumerRecord consumerRecord = new ConsumerRecord("topic", 1, 152, 11125, 130 | TimestampType.LOG_APPEND_TIME, null, 0, 0, null, null, new RecordHeaders()); 131 | consumerRecord.headers().add(IOEventHeaders.OUTPUT_EVENT.toString(), "output name".getBytes()); 132 | consumerRecord.headers().add(IOEventHeaders.CORRELATION_ID.toString(), "id".getBytes()); 133 | consumerRecord.headers().add(IOEventHeaders.PROCESS_NAME.toString(), "workflow name".getBytes()); 134 | consumerRecord.headers().add("another header", "value".getBytes()); 135 | 136 | IOEventParallelEventInformation parallelEventInfo = new IOEventParallelEventInformation(consumerRecord, 137 | ioeventRecordInfo, new BeanMethodPair(this, method, null), Arrays.asList("Output 1", "Output 2"), 138 | "appNam"); 139 | 140 | Message messageResult = recordsHandler.sendParallelInfo(parallelEventInfo); 141 | 142 | Message message = MessageBuilder.withPayload(parallelEventInfo) 143 | .setHeader(KafkaHeaders.TOPIC, "ioevent-parallel-gateway-events") 144 | .setHeader(KafkaHeaders.KEY, 145 | parallelEventInfo.getHeaders().get(IOEventHeaders.CORRELATION_ID.toString()).toString() + parallelEventInfo.getInputRequired()) 146 | .build(); 147 | 148 | assertEquals(message.getHeaders().get("kafka_messageKey"), messageResult.getHeaders().get("kafka_messageKey")); 149 | } 150 | 151 | @Test 152 | void parallelInvokeMethodTest() throws NoSuchMethodException, SecurityException { 153 | Method method = this.getClass().getMethod("init", null); 154 | CompletableFuture> future = new CompletableFuture<>(); 155 | when(kafkaTemplate.send(Mockito.any(Message.class))).thenReturn(future); 156 | when(ioEventService.getInputNames(Mockito.any())).thenReturn(Arrays.asList("Output 1", "Output 2")); 157 | IOEventRecordInfo ioeventRecordInfo = new IOEventRecordInfo("1155", "process name", "Output 1", new StopWatch(),1000L,null); 158 | ConsumerRecord consumerRecord = new ConsumerRecord("topic", 1, 152, 11125, 159 | TimestampType.LOG_APPEND_TIME, null, 0, 0, null, null, new RecordHeaders()); 160 | consumerRecord.headers().add(IOEventHeaders.OUTPUT_EVENT.toString(), "output name".getBytes()); 161 | consumerRecord.headers().add(IOEventHeaders.CORRELATION_ID.toString(), "id".getBytes()); 162 | consumerRecord.headers().add(IOEventHeaders.PROCESS_NAME.toString(), "workflow name".getBytes()); 163 | consumerRecord.headers().add("another header", "value".getBytes()); 164 | 165 | recordsHandler.parallelInvoke(new BeanMethodPair(this, method, null), consumerRecord, ioeventRecordInfo); 166 | 167 | assertThatNoException(); 168 | 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/test/java/com/ioevent/starter/configuration/aspect/v2/IOEventEndAspectTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | 19 | 20 | package com.ioevent.starter.configuration.aspect.v2; 21 | 22 | 23 | 24 | 25 | 26 | 27 | import static org.assertj.core.api.Assertions.assertThatNoException; 28 | import static org.junit.jupiter.api.Assertions.assertEquals; 29 | import static org.mockito.Mockito.mock; 30 | import static org.mockito.Mockito.when; 31 | 32 | import java.lang.reflect.Method; 33 | import java.text.ParseException; 34 | import java.util.ArrayList; 35 | import java.util.Arrays; 36 | import java.util.Date; 37 | import java.util.HashMap; 38 | import java.util.Map; 39 | import java.util.concurrent.CompletableFuture; 40 | 41 | import org.aspectj.lang.JoinPoint; 42 | import org.aspectj.lang.reflect.MethodSignature; 43 | import org.junit.Assert; 44 | import org.junit.jupiter.api.BeforeEach; 45 | import org.junit.jupiter.api.Test; 46 | import org.mockito.InjectMocks; 47 | import org.mockito.Mock; 48 | import org.mockito.Mockito; 49 | import org.mockito.MockitoAnnotations; 50 | import org.springframework.kafka.core.KafkaTemplate; 51 | import org.springframework.kafka.support.KafkaHeaders; 52 | import org.springframework.kafka.support.SendResult; 53 | import org.springframework.messaging.Message; 54 | import org.springframework.messaging.support.MessageBuilder; 55 | import org.springframework.util.StopWatch; 56 | 57 | import com.fasterxml.jackson.core.JsonProcessingException; 58 | import com.ioevent.starter.annotations.EndEvent; 59 | import com.ioevent.starter.annotations.IOEvent; 60 | import com.ioevent.starter.annotations.IOResponse; 61 | import com.ioevent.starter.annotations.InputEvent; 62 | import com.ioevent.starter.annotations.OutputEvent; 63 | import com.ioevent.starter.configuration.properties.IOEventProperties; 64 | import com.ioevent.starter.domain.IOEventHeaders; 65 | import com.ioevent.starter.domain.IOEventType; 66 | import com.ioevent.starter.handler.IOEventRecordInfo; 67 | import com.ioevent.starter.logger.EventLogger; 68 | import com.ioevent.starter.service.IOEventContextHolder; 69 | import com.ioevent.starter.service.IOEventService; 70 | 71 | class IOEventEndAspectTest { 72 | @InjectMocks 73 | IOEventEndAspect endAspect = new IOEventEndAspect(); 74 | @Mock 75 | IOEventService ioEventService; 76 | @Mock 77 | IOEventProperties iOEventProperties; 78 | @Mock 79 | JoinPoint joinPoint; 80 | @Mock 81 | KafkaTemplate kafkaTemplate; 82 | @Mock 83 | IOEventRecordInfo ioeventRecordInfo; 84 | 85 | @BeforeEach 86 | public void init() { 87 | 88 | MockitoAnnotations.initMocks(this); 89 | } 90 | 91 | /** method to test annotations **/ 92 | @IOEvent(key = "terminate the event", topic = "Topic", // 93 | input = @InputEvent(key = "previous Task"), // 94 | endEvent = @EndEvent(key = "process name")// 95 | ) 96 | public boolean endAnnotationMethod() { 97 | return true; 98 | } 99 | 100 | /** method to test annotations **/ 101 | @IOEvent(key = "stepname",input = @InputEvent(key = "previous Task"), // 102 | endEvent = @EndEvent(key = "process name")) 103 | public boolean endAnnotationMethod2() { 104 | return true; 105 | } 106 | 107 | /** method to test annotations **/ 108 | @IOEvent(key = "test annotation", topic = "topic1", // 109 | input = @InputEvent(key = "input", topic = "T"), output = @OutputEvent(key = "output", topic = "T")) 110 | public boolean simpleTaskAnnotationMethod() { 111 | return true; 112 | } 113 | 114 | @Test 115 | void testTryAnnotationmethod() { 116 | IOEventEndAspectTest serviceTest = Mockito.spy(this); 117 | Assert.assertEquals(true, endAnnotationMethod()); 118 | Assert.assertEquals(true, endAnnotationMethod2()); 119 | Assert.assertEquals(true, simpleTaskAnnotationMethod()); 120 | 121 | } 122 | 123 | @Test 124 | void buildEventMessageTest() throws NoSuchMethodException, SecurityException { 125 | when(iOEventProperties.getPrefix()).thenReturn("test-"); 126 | when(ioEventService.getOutputTopicName(Mockito.any(IOEvent.class), Mockito.any(), Mockito.any(String.class))).thenReturn(""); 127 | Method method = this.getClass().getMethod("endAnnotationMethod", null); 128 | 129 | IOEvent ioEvent = method.getAnnotation(IOEvent.class); 130 | Map headersMap=new HashMap<>(); 131 | IOEventRecordInfo ioeventRecordInfo = new IOEventRecordInfo("1155", "process name", "_", new StopWatch(),100L,null); 132 | IOResponse ioEventResponse = new IOResponse<>(null, "payload", null); 133 | Message messageResult = endAspect.buildEventMessage(ioEvent,null, ioEventResponse, "END", ioeventRecordInfo, (long) 123546, headersMap, "example"); 134 | Message message = MessageBuilder.withPayload("payload").setHeader(KafkaHeaders.TOPIC, "test-topic") 135 | .setHeader(KafkaHeaders.KEY, "1155").setHeader(IOEventHeaders.CORRELATION_ID.toString(), "1155") 136 | .setHeader(IOEventHeaders.STEP_NAME.toString(), "terminate the event").setHeader(IOEventHeaders.EVENT_TYPE.toString(), IOEventType.END.toString()) 137 | .setHeader(IOEventHeaders.INPUT.toString(), new ArrayList(Arrays.asList("previous Task"))) 138 | .setHeader(IOEventHeaders.OUTPUT_EVENT.toString(), "END").setHeader(IOEventHeaders.PROCESS_NAME.toString(), "process name") 139 | .setHeader(IOEventHeaders.START_TIME.toString(), (long) 123546).build(); 140 | 141 | assertEquals(message.getHeaders().get(IOEventHeaders.STEP_NAME.toString()), messageResult.getHeaders().get(IOEventHeaders.STEP_NAME.toString())); 142 | assertEquals(message.getHeaders().get("kafka_messageKey"), messageResult.getHeaders().get("kafka_messageKey")); 143 | 144 | Method method2 = this.getClass().getMethod("endAnnotationMethod2", null); 145 | IOEvent ioEvent2 = method2.getAnnotation(IOEvent.class); 146 | Message messageResult2 = endAspect.buildEventMessage(ioEvent2,null, ioEventResponse, "END", ioeventRecordInfo, (long) 123546, headersMap, "example"); 147 | assertEquals("test-", messageResult2.getHeaders().get("kafka_topic")); 148 | 149 | } 150 | 151 | @Test 152 | void prepareAndDisplayEventLoggerTest() throws JsonProcessingException, NoSuchMethodException, SecurityException, ParseException { 153 | 154 | when(joinPoint.getArgs()).thenReturn(new String[] { "payload" }); 155 | Method method = this.getClass().getMethod("endAnnotationMethod", null); 156 | IOEvent ioEvent = method.getAnnotation(IOEvent.class); 157 | StopWatch watch = new StopWatch(); 158 | EventLogger eventLogger = new EventLogger(); 159 | eventLogger.startEventLog(); 160 | eventLogger.setEndTime(eventLogger.getISODate(new Date())); 161 | 162 | watch.start("IOEvent annotation End Aspect"); 163 | endAspect.prepareAndDisplayEventLogger(eventLogger, ioEvent, "payload" , watch, 164 | ioeventRecordInfo); 165 | assertThatNoException(); 166 | 167 | } 168 | 169 | @Test 170 | void iOEventAnnotationAspectTest() throws Throwable { 171 | Method method = this.getClass().getMethod("endAnnotationMethod", null); 172 | IOEvent ioEvent = method.getAnnotation(IOEvent.class); 173 | Method method2 = this.getClass().getMethod("simpleTaskAnnotationMethod", null); 174 | IOEvent ioEvent2 = method2.getAnnotation(IOEvent.class); 175 | CompletableFuture> future = new CompletableFuture<>(); 176 | StopWatch watch = new StopWatch(); 177 | watch.start("IOEvent annotation End Aspect"); 178 | IOEventContextHolder.setContext(ioeventRecordInfo); 179 | when(ioeventRecordInfo.getWatch()).thenReturn(watch); 180 | when(kafkaTemplate.send(Mockito.any(Message.class))).thenReturn(future); 181 | when(ioEventService.getOutputs(ioEvent)).thenReturn(Arrays.asList(ioEvent.output())); 182 | when(iOEventProperties.getPrefix()).thenReturn("test-"); 183 | when(joinPoint.getArgs()).thenReturn(new String[] { "payload" }); 184 | MethodSignature methodSignature = mock(MethodSignature.class); 185 | when(methodSignature.getMethod()).thenReturn(method ); 186 | when(joinPoint.getSignature()).thenReturn(methodSignature); 187 | endAspect.iOEventAnnotationAspect(joinPoint, ioEvent, "payload"); 188 | endAspect.iOEventAnnotationAspect(joinPoint, ioEvent2, "payload"); 189 | assertThatNoException(); 190 | 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/configuration/IOEventConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.configuration; 18 | 19 | import java.util.ArrayList; 20 | import java.util.HashSet; 21 | import java.util.LinkedList; 22 | import java.util.List; 23 | import java.util.Set; 24 | import java.util.UUID; 25 | import java.util.concurrent.ScheduledExecutorService; 26 | import java.util.concurrent.ScheduledThreadPoolExecutor; 27 | 28 | import org.springframework.beans.factory.annotation.Value; 29 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 30 | import org.springframework.context.annotation.Bean; 31 | import org.springframework.context.annotation.Configuration; 32 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 33 | import org.springframework.context.annotation.Import; 34 | import org.springframework.kafka.annotation.EnableKafka; 35 | import org.springframework.kafka.annotation.EnableKafkaStreams; 36 | import org.springframework.scheduling.annotation.EnableAsync; 37 | import org.springframework.scheduling.annotation.EnableScheduling; 38 | import org.springframework.stereotype.Service; 39 | 40 | import com.fasterxml.jackson.databind.ObjectMapper; 41 | import com.ioevent.starter.configuration.aspect.v2.IOEvenImplicitTaskAspect; 42 | import com.ioevent.starter.configuration.aspect.v2.IOEventEndAspect; 43 | import com.ioevent.starter.configuration.aspect.v2.IOEventStartAspect; 44 | import com.ioevent.starter.configuration.aspect.v2.IOEventTransitionAspect; 45 | import com.ioevent.starter.configuration.aspect.v2.IOExceptionHandlingAspect; 46 | import com.ioevent.starter.configuration.context.AppContext; 47 | import com.ioevent.starter.configuration.kafka.KafkaConfig; 48 | import com.ioevent.starter.configuration.postprocessor.IOEventBpmnPostProcessor; 49 | import com.ioevent.starter.configuration.postprocessor.IOEventTopicBeanPostProcessor; 50 | import com.ioevent.starter.configuration.properties.IOEventProperties; 51 | import com.ioevent.starter.controller.IOEventController; 52 | import com.ioevent.starter.domain.IOEventBpmnPart; 53 | import com.ioevent.starter.handler.RecordsHandler; 54 | import com.ioevent.starter.listener.IOEventParrallelListener; 55 | import com.ioevent.starter.listener.IOEventTimerListener; 56 | import com.ioevent.starter.listener.Listener; 57 | import com.ioevent.starter.listener.ListenerCreator; 58 | import com.ioevent.starter.listener.MessageListener; 59 | import com.ioevent.starter.service.IOEventMessageBuilderService; 60 | import com.ioevent.starter.service.IOEventRegistryService; 61 | import com.ioevent.starter.service.IOEventService; 62 | import com.ioevent.starter.service.TopicServices; 63 | import com.ioevent.starter.stream.MessageStream; 64 | import com.ioevent.starter.stream.ParallelStream; 65 | import com.ioevent.starter.stream.TimerStream; 66 | 67 | import lombok.RequiredArgsConstructor; 68 | import lombok.extern.slf4j.Slf4j; 69 | 70 | /** 71 | * class for ioevent configuration which contains all configurations needed by a 72 | * project which use IOEvent 73 | **/ 74 | @Slf4j 75 | @EnableKafka 76 | @Configuration 77 | @EnableAspectJAutoProxy(proxyTargetClass = true) 78 | @EnableKafkaStreams 79 | @EnableScheduling 80 | @EnableAsync 81 | @Import({ KafkaConfig.class }) 82 | @Service 83 | @RequiredArgsConstructor 84 | public class IOEventConfiguration { 85 | 86 | ObjectMapper mapper = new ObjectMapper(); 87 | 88 | @Value("${spring.application.name}") 89 | private String appName; 90 | 91 | @Value("${ioevent.core_pool_size:2}") 92 | private int core_pool_size ; 93 | 94 | 95 | @Bean 96 | public ParallelStream parallelStream() { 97 | return new ParallelStream(); 98 | } 99 | @Bean 100 | public MessageStream messageStream() { 101 | return new MessageStream(); 102 | } 103 | 104 | @Bean 105 | public TimerStream timerStream() { 106 | return new TimerStream(); 107 | } 108 | 109 | 110 | @ConditionalOnMissingBean 111 | @Bean 112 | public MessageListener messageListener() { 113 | return new MessageListener(); 114 | } 115 | 116 | @ConditionalOnMissingBean 117 | @Bean 118 | public IOEventParrallelListener ioEventParrallelListener() { 119 | return new IOEventParrallelListener(); 120 | } 121 | 122 | @ConditionalOnMissingBean 123 | @Bean 124 | public IOEventTimerListener ioEventTimerListener() { 125 | return new IOEventTimerListener(); 126 | } 127 | 128 | @Bean 129 | public AppContext appContext() { 130 | return new AppContext(); 131 | } 132 | 133 | @ConditionalOnMissingBean 134 | @Bean 135 | public IOEventProperties ioEventProperties() { 136 | return new IOEventProperties(); 137 | } 138 | 139 | @Bean 140 | public TopicServices topicServices() { 141 | return new TopicServices(); 142 | } 143 | 144 | @ConditionalOnMissingBean 145 | @Bean 146 | public RecordsHandler recordsHandler() { 147 | return new RecordsHandler(); 148 | } 149 | 150 | @Bean 151 | public ListenerCreator listenerCreator() { 152 | return new ListenerCreator(); 153 | } 154 | 155 | // @Bean 156 | // public Executor asyncExecutor() { 157 | // ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 158 | // executor.setCorePoolSize(5); 159 | // executor.setMaxPoolSize(10); 160 | // //executor.setQueueCapacity(500); 161 | // executor.setThreadNamePrefix("Asynchronous Process-"); 162 | // executor.initialize(); 163 | // return executor; 164 | // } 165 | 166 | @Bean 167 | public ScheduledExecutorService asyncExecutor() { 168 | return new ScheduledThreadPoolExecutor(core_pool_size); 169 | } 170 | 171 | @ConditionalOnMissingBean 172 | @Bean 173 | public IOEventTopicBeanPostProcessor ioEventTopicBeanPostProcessor() { 174 | return new IOEventTopicBeanPostProcessor(); 175 | } 176 | 177 | @ConditionalOnMissingBean 178 | @Bean 179 | public IOEventBpmnPostProcessor ioEventBpmnPostProcessor() { 180 | return new IOEventBpmnPostProcessor(); 181 | } 182 | 183 | @ConditionalOnMissingBean 184 | @Bean 185 | public IOEventStartAspect ioEventStartAspect() { 186 | return new IOEventStartAspect(); 187 | } 188 | 189 | @ConditionalOnMissingBean 190 | @Bean 191 | public IOEventTransitionAspect ioEventTransitionAspect() { 192 | return new IOEventTransitionAspect(); 193 | } 194 | 195 | @ConditionalOnMissingBean 196 | @Bean 197 | public IOExceptionHandlingAspect ioExceptionHandlingAspect() { 198 | return new IOExceptionHandlingAspect(); 199 | } 200 | 201 | @ConditionalOnMissingBean 202 | @Bean 203 | public IOEventEndAspect ioEventEndAspect() { 204 | return new IOEventEndAspect(); 205 | } 206 | 207 | @ConditionalOnMissingBean 208 | @Bean 209 | public IOEvenImplicitTaskAspect ioEvenImplicitTaskAspect() { 210 | return new IOEvenImplicitTaskAspect(); 211 | } 212 | 213 | @ConditionalOnMissingBean 214 | @Bean 215 | public IOEventController ioEventController() { 216 | return new IOEventController(); 217 | } 218 | 219 | @Bean("iobpmnlist") 220 | public List iobpmnlist() { 221 | return new LinkedList<>(); 222 | } 223 | 224 | @Bean("ioTopics") 225 | public Set ioTopics() { 226 | return new HashSet<>(); 227 | } 228 | 229 | @Bean("apiKeys") 230 | public Set apiKeys() { 231 | return new HashSet<>(); 232 | } 233 | 234 | @Bean("listeners") 235 | public List listeners() { 236 | return new ArrayList<>(); 237 | } 238 | 239 | @ConditionalOnMissingBean 240 | @Bean 241 | public IOEventService ioEventService() { 242 | return new IOEventService(); 243 | } 244 | 245 | @ConditionalOnMissingBean 246 | @Bean 247 | public IOEventMessageBuilderService ioeventMessageBuilderService() { 248 | return new IOEventMessageBuilderService(); 249 | } 250 | 251 | @Bean 252 | public IOEventRegistryService ioeventRegistryService() { 253 | return new IOEventRegistryService(); 254 | } 255 | 256 | @Bean("instanceID") 257 | public UUID instanceID() { 258 | return UUID.randomUUID(); 259 | } 260 | 261 | } 262 | -------------------------------------------------------------------------------- /src/main/java/com/ioevent/starter/configuration/aspect/v2/IOEventEndAspect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 CodeOnce Software (https://www.codeonce.fr/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ioevent.starter.configuration.aspect.v2; 18 | 19 | import java.text.ParseException; 20 | import java.util.Arrays; 21 | import java.util.Date; 22 | import java.util.Map; 23 | import java.util.concurrent.ExecutionException; 24 | 25 | import org.aspectj.lang.JoinPoint; 26 | import org.aspectj.lang.annotation.AfterReturning; 27 | import org.aspectj.lang.annotation.Aspect; 28 | import org.aspectj.lang.reflect.MethodSignature; 29 | import org.springframework.beans.factory.annotation.Autowired; 30 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 31 | import org.springframework.context.annotation.Configuration; 32 | import org.springframework.kafka.core.KafkaTemplate; 33 | import org.springframework.kafka.support.KafkaHeaders; 34 | import org.springframework.messaging.Message; 35 | import org.springframework.messaging.support.MessageBuilder; 36 | import org.springframework.util.StopWatch; 37 | 38 | import com.fasterxml.jackson.core.JsonProcessingException; 39 | import com.fasterxml.jackson.databind.ObjectMapper; 40 | import com.ioevent.starter.annotations.IOEvent; 41 | import com.ioevent.starter.annotations.IOFlow; 42 | import com.ioevent.starter.annotations.IOResponse; 43 | import com.ioevent.starter.configuration.properties.IOEventProperties; 44 | import com.ioevent.starter.domain.IOEventHeaders; 45 | import com.ioevent.starter.domain.IOEventType; 46 | import com.ioevent.starter.enums.EventTypesEnum; 47 | import com.ioevent.starter.handler.IOEventRecordInfo; 48 | import com.ioevent.starter.logger.EventLogger; 49 | import com.ioevent.starter.service.IOEventContextHolder; 50 | import com.ioevent.starter.service.IOEventService; 51 | 52 | import lombok.extern.slf4j.Slf4j; 53 | 54 | /** 55 | * Aspect class for advice associated with a @IOEvent calls for End task event 56 | * type 57 | */ 58 | @Slf4j 59 | @Aspect 60 | @Configuration 61 | @ConditionalOnExpression("${false}") 62 | public class IOEventEndAspect { 63 | 64 | @Autowired 65 | private KafkaTemplate kafkaTemplate; 66 | 67 | @Autowired 68 | private IOEventProperties iOEventProperties; 69 | 70 | private ObjectMapper mapper = new ObjectMapper(); 71 | 72 | @Autowired 73 | private IOEventService ioEventService; 74 | 75 | /** 76 | * Method AfterReturning advice runs after a successful completion of a End task 77 | * with IOEvent annotation, 78 | * 79 | * @param joinPoint for the join point during the execution of the program, 80 | * @param ioEvent for ioevent annotation which include task information, 81 | * @param returnObject for the returned object, 82 | * @throws JsonProcessingException 83 | * @throws ExecutionException 84 | * @throws InterruptedException 85 | * @throws ParseException 86 | */ 87 | @AfterReturning(value = "@annotation(anno)", argNames = "jp, anno,return", returning = "return") 88 | public void iOEventAnnotationAspect(JoinPoint joinPoint, IOEvent ioEvent, Object returnObject) 89 | throws JsonProcessingException, InterruptedException, ExecutionException, ParseException { 90 | MethodSignature signature = (MethodSignature) joinPoint.getSignature(); 91 | IOEvent myAnnotation = signature.getMethod().getAnnotation(IOEvent.class); 92 | if (ioEventService.isEnd(ioEvent)) { 93 | IOEventRecordInfo ioeventRecordInfo = IOEventContextHolder.getContext(); 94 | Map headers = ioEventService.prepareHeaders(ioeventRecordInfo.getHeaderList(), 95 | ioEventService.getpayload(joinPoint, returnObject).getHeaders()); 96 | 97 | StopWatch watch = ioeventRecordInfo.getWatch(); 98 | EventLogger eventLogger = new EventLogger(); 99 | eventLogger.startEventLog(); 100 | eventLogger.setStartTime(eventLogger.getISODate(new Date(ioeventRecordInfo.getStartTime()))); 101 | IOFlow ioFlow = joinPoint.getTarget().getClass().getAnnotation(IOFlow.class); 102 | ioeventRecordInfo.setWorkFlowName( 103 | ioEventService.getProcessName(ioEvent, ioFlow, ioeventRecordInfo.getWorkFlowName())); 104 | IOResponse payload = ioEventService.getpayload(joinPoint, returnObject); 105 | String output = "END"; 106 | Message message = this.buildEventMessage(ioEvent, ioFlow, payload, output, ioeventRecordInfo, 107 | ioeventRecordInfo.getStartTime(), headers,""); 108 | Long eventTimeStamp = kafkaTemplate.send(message).get().getRecordMetadata().timestamp(); 109 | eventLogger.setEndTime(eventLogger.getISODate(new Date(eventTimeStamp))); 110 | prepareAndDisplayEventLogger(eventLogger, ioEvent, payload.getBody(), watch, ioeventRecordInfo); 111 | } 112 | } 113 | 114 | /** 115 | * Method that build the event message of End task to be send in kafka topic, 116 | * 117 | * @param ioEvent for ioevent annotation which include task 118 | * information, 119 | * @param ioFlow for ioflow annotation which include general 120 | * information, 121 | * @param payload for the payload of the event, 122 | * @param outputEvent for the output Event where the event will send , 123 | * @param ioeventRecordInfo for the record information from the consumed event, 124 | * @param startTime for the start time of the event, 125 | * @param headers 126 | * @return message type of Message, 127 | */ 128 | public Message buildEventMessage(IOEvent ioEvent, IOFlow ioFlow, IOResponse payload, 129 | String outputEvent, IOEventRecordInfo ioeventRecordInfo, Long startTime, Map headers,String key) { 130 | String topicName = ioEventService.getOutputTopicName(ioEvent, ioFlow, ""); 131 | String apiKey = ioEventService.getApiKey(iOEventProperties, ioFlow); 132 | return MessageBuilder.withPayload(payload.getBody()).copyHeaders(headers) 133 | .setHeader(KafkaHeaders.TOPIC, iOEventProperties.getPrefix() + topicName) 134 | .setHeader(KafkaHeaders.KEY, ioeventRecordInfo.getId()) 135 | .setHeader(IOEventHeaders.MESSAGE_KEY.toString(), key) 136 | .setHeader(IOEventHeaders.PROCESS_NAME.toString(), ioeventRecordInfo.getWorkFlowName()) 137 | .setHeader(IOEventHeaders.OUTPUT_EVENT.toString(), outputEvent) 138 | .setHeader(IOEventHeaders.CORRELATION_ID.toString(), ioeventRecordInfo.getId()) 139 | .setHeader(IOEventHeaders.EVENT_TYPE.toString(), IOEventType.END.toString()) 140 | .setHeader(IOEventHeaders.INPUT.toString(), Arrays.asList(ioeventRecordInfo.getOutputConsumedName())) 141 | .setHeader(IOEventHeaders.STEP_NAME.toString(), ioEvent.key()) 142 | .setHeader(IOEventHeaders.API_KEY.toString(), apiKey) 143 | .setHeader(IOEventHeaders.START_TIME.toString(), startTime) 144 | .setHeader(IOEventHeaders.START_INSTANCE_TIME.toString(), ioeventRecordInfo.getInstanceStartTime()) 145 | .setHeader(IOEventHeaders.IMPLICIT_START.toString(), false) 146 | .setHeader(IOEventHeaders.IMPLICIT_END.toString(), false).build(); 147 | } 148 | 149 | /** 150 | * Method that display logs after task completed , 151 | * 152 | * @param eventLogger for the log info dto display, 153 | * @param ioEvent for ioevent annotation which include task 154 | * information, 155 | * @param payload for the payload of the event, 156 | * @param watch for capturing time, 157 | * @param ioeventRecordInfo for the record information from the consumed event, 158 | * @throws ParseException 159 | */ 160 | public void prepareAndDisplayEventLogger(EventLogger eventLogger, IOEvent ioEvent, Object payload, StopWatch watch, 161 | IOEventRecordInfo ioeventRecordInfo) throws JsonProcessingException, ParseException { 162 | 163 | watch.stop(); 164 | eventLogger.loggerSetting(ioeventRecordInfo.getId(), ioeventRecordInfo.getWorkFlowName(), ioEvent.key(), 165 | ioeventRecordInfo.getOutputConsumedName(), "__", "End", payload); 166 | eventLogger.stopEvent(); 167 | String jsonObject = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(eventLogger); 168 | log.info(jsonObject); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 13 | 4.0.0 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 3.1.5 18 | 19 | 20 | io.ioevent 21 | ioevent-spring-boot-starter 22 | 2.2.3 23 | ioevent-spring-boot-starter 24 | IOEvent is an Event-Driven 25 | microservices framework 26 | 27 | 17 28 | 2022.0.4 29 | 1.18.24 30 | 0.18.0 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter 36 | 37 | 38 | 39 | ch.qos.logback 40 | logback-classic 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-web 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-autoconfigure 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-aop 55 | 56 | 57 | org.apache.kafka 58 | kafka-streams 59 | 60 | 61 | org.springframework.kafka 62 | spring-kafka 63 | 64 | 65 | org.springframework.cloud 66 | spring-cloud-stream-binder-kafka-streams 67 | 68 | 69 | 70 | com.google.code.gson 71 | gson 72 | 2.8.7 73 | 74 | 75 | 76 | 77 | org.springdoc 78 | springdoc-openapi-ui 79 | 1.6.6 80 | 81 | 82 | 83 | com.fasterxml.jackson.core 84 | jackson-core 85 | 86 | 87 | com.fasterxml.jackson.core 88 | jackson-databind 89 | 90 | 91 | org.projectlombok 92 | lombok 93 | ${org.projectlombok.version} 94 | provided 95 | 96 | 97 | org.apache.commons 98 | commons-lang3 99 | 100 | 101 | org.springframework.boot 102 | spring-boot-starter-test 103 | test 104 | 105 | 106 | junit 107 | junit 108 | test 109 | 110 | 111 | 112 | 113 | 114 | org.springframework.cloud 115 | spring-cloud-dependencies 116 | ${spring-cloud.version} 117 | pom 118 | import 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-javadoc-plugin 129 | 3.3.1 130 | 131 | 132 | 133 | 134 | 135 | 136 | org.apache.maven.plugins 137 | maven-deploy-plugin 138 | ${maven-deploy-plugin.version} 139 | 140 | 141 | com.mycila 142 | license-maven-plugin 143 | 4.1 144 | 145 | 146 | CodeOnce Software 147 | https://www.codeonce.fr/ 148 | 149 | 150 | 151 |
com/mycila/maven/plugin/license/templates/APACHE-2.txt 152 |
153 | 154 | **/README 155 | src/test/resources/** 156 | src/main/resources/** 157 | 158 |
159 |
160 |
161 |
162 | 163 | 164 | org.apache.maven.plugins 165 | maven-site-plugin 166 | 3.7.1 167 | 168 | 169 | 170 | org.apache.maven.plugins 171 | maven-project-info-reports-plugin 172 | 3.0.0 173 | 174 | 175 | 176 | org.apache.maven.plugins 177 | maven-javadoc-plugin 178 | 3.3.1 179 | 180 | 181 | attach-javadocs 182 | 183 | jar 184 | 185 | 186 | 187 | 188 | false 189 | 190 | 191 | 192 | org.apache.maven.plugins 193 | maven-source-plugin 194 | 195 | 196 | attach-sources 197 | 198 | jar 199 | 200 | 201 | 202 | 203 | 204 | org.jacoco 205 | jacoco-maven-plugin 206 | 0.8.7 207 | 208 | true 209 | 210 | 211 | 212 | 213 | prepare-agent 214 | 215 | 216 | 217 | post-unit-test 218 | test 219 | 220 | report 221 | 222 | 223 | 224 | 225 | 226 | com.amashchenko.maven.plugin 227 | gitflow-maven-plugin 228 | 1.12.0 229 | 230 | 231 | 232 | 233 | 234 | 235 | org.apache.maven.plugins 236 | maven-gpg-plugin 237 | 3.0.1 238 | 239 | 240 | sign-artifacts 241 | verify 242 | 243 | sign 244 | 245 | 246 | 247 | 248 | 249 |
250 |
251 | 252 | 253 | 254 | 255 | org.apache.maven.plugins 256 | maven-javadoc-plugin 257 | 3.3.1 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | The Apache Software License, Version 2.0 266 | http://www.apache.org/licenses/LICENSE-2.0.txt 267 | 268 | 269 | 270 | CodeOnce Software 271 | https://www.codeonce.fr/ 272 | 273 | http://www.ioevent.io 274 | 2021 275 | 276 | 277 | ioevent.io 278 | ioevent Team 279 | CodeOnce Software 280 | https://www.codeonce.fr/ 281 | 282 | 283 | 284 | 285 | https://github.com/ioevent-io/ioevent-spring-starter/tree/master 286 | scm:git:git://github.com:ioevent-io/ioevent-spring-starter.git 287 | scm:git:ssh://github.com:ioevent-io/ioevent-spring-starter.git 288 | 289 | 290 | 291 | sonatype-nexus-snapshots 292 | Sonatype Nexus Snapshots 293 | https://s01.oss.sonatype.org/content/repositories/snapshots 294 | 295 | 296 | sonatype-nexus-staging 297 | Nexus Release Repository 298 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2 299 | 300 | 301 | 302 | 303 |
304 | --------------------------------------------------------------------------------