Skip to content

Commit

Permalink
feat(simulator): different running modes
Browse files Browse the repository at this point in the history
  • Loading branch information
bbortt committed Feb 9, 2024
1 parent cadb91b commit 7da5a64
Show file tree
Hide file tree
Showing 12 changed files with 216 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ citrus.simulator.default-scenario=Default

# Should Citrus validate incoming messages on syntax and semantics
citrus.simulator.template-validation=true

# Use async mode for intermediate messaging
citrus.simulator.mode=async
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ citrus.simulator.template-validation=true
# JMS destinations
citrus.simulator.jms.inbound-destination=Fax.Inbound
citrus.simulator.jms.status-destination=Fax.Status

# Use async mode for intermediate messaging
citrus.simulator.mode=async
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ citrus.simulator.default-scenario=Default

# Should Citrus validate incoming messages on syntax and semantics
citrus.simulator.template-validation=true

# Use async mode for intermediate messaging
citrus.simulator.mode=async
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ citrus.simulator.default-scenario=Default

# Should Citrus validate incoming messages on syntax and semantics
citrus.simulator.template-validation=true

# Use async mode for intermediate messaging
citrus.simulator.mode=async
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ citrus.simulator.default-scenario=Default

# Should Citrus validate incoming messages on syntax and semantics
citrus.simulator.template-validation=true

# Use async mode for intermediate messaging
citrus.simulator.mode=async
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 the original author or authors.
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,9 +19,6 @@
import jakarta.annotation.Nullable;
import org.citrusframework.simulator.model.ScenarioParameter;
import org.citrusframework.simulator.scenario.SimulatorScenario;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;

import java.util.List;

Expand All @@ -34,7 +31,7 @@
* the "CRUD Service" for {@link org.citrusframework.simulator.model.ScenarioExecution} and has
* nothing to do with actual {@link SimulatorScenario} execution.
*/
public interface ScenarioExecutorService extends DisposableBean, ApplicationListener<ContextClosedEvent> {
public interface ScenarioExecutorService {

/**
* Starts a new scenario instance using the collection of supplied parameters.
Expand All @@ -43,7 +40,7 @@ public interface ScenarioExecutorService extends DisposableBean, ApplicationList
* @param scenarioParameters the list of parameters to pass to the scenario when starting
* @return the scenario execution id
*/
public Long run(String name, @Nullable List<ScenarioParameter> scenarioParameters);
Long run(String name, @Nullable List<ScenarioParameter> scenarioParameters);

/**
* Starts a new scenario instance using the collection of supplied parameters.
Expand All @@ -53,5 +50,5 @@ public interface ScenarioExecutorService extends DisposableBean, ApplicationList
* @param scenarioParameters the list of parameters to pass to the scenario when starting
* @return the scenario execution id
*/
public Long run(SimulatorScenario scenario, String name, @Nullable List<ScenarioParameter> scenarioParameters);
Long run(SimulatorScenario scenario, String name, @Nullable List<ScenarioParameter> scenarioParameters);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.citrusframework.simulator.service.runner;

import jakarta.annotation.Nullable;
import org.citrusframework.Citrus;
import org.citrusframework.context.TestContext;
import org.citrusframework.simulator.model.ScenarioExecution;
import org.citrusframework.simulator.model.ScenarioParameter;
import org.citrusframework.simulator.scenario.ScenarioRunner;
import org.citrusframework.simulator.scenario.SimulatorScenario;
import org.citrusframework.simulator.service.ScenarioExecutionService;
import org.citrusframework.simulator.service.ScenarioExecutorService;
import org.springframework.context.ApplicationContext;

import java.util.List;

import static org.citrusframework.annotations.CitrusAnnotations.injectAll;
import static org.citrusframework.simulator.model.ScenarioExecution.EXECUTION_ID;

abstract class AbstractLegacyScenarioExecutorService implements ScenarioExecutorService {

private final ApplicationContext applicationContext;
private final Citrus citrus;
private final ScenarioExecutionService scenarioExecutionService;

public AbstractLegacyScenarioExecutorService(ApplicationContext applicationContext, Citrus citrus, ScenarioExecutionService scenarioExecutionService) {
this.applicationContext = applicationContext;
this.citrus = citrus;
this.scenarioExecutionService = scenarioExecutionService;
}

@Override
public final Long run(String name, @Nullable List<ScenarioParameter> scenarioParameters) {
return run(applicationContext.getBean(name, SimulatorScenario.class), name, scenarioParameters);
}

@Override
public final Long run(SimulatorScenario scenario, String name, @Nullable List<ScenarioParameter> scenarioParameters) {
ScenarioExecution scenarioExecution = scenarioExecutionService.createAndSaveExecutionScenario(name, scenarioParameters);

prepareBeforeExecution(scenario);

startScenario(scenarioExecution.getExecutionId(), name, scenario, scenarioParameters);

return scenarioExecution.getExecutionId();
}

protected abstract void startScenario(Long executionId, String name, SimulatorScenario scenario, List<ScenarioParameter> scenarioParameters);

/**
* Prepare scenario instance before execution. Subclasses can add custom preparation steps in
* here.
*
* @param scenario
*/
protected void prepareBeforeExecution(SimulatorScenario scenario) {
}

protected TestContext createTestContext() {
return citrus.getCitrusContext().createTestContext();
}

protected void createAndRunScenarioRunner(TestContext context, Long executionId, String name, SimulatorScenario scenario, List<ScenarioParameter> scenarioParameters) {
var runner = new ScenarioRunner(scenario.getScenarioEndpoint(), applicationContext, context);
if (scenarioParameters != null) {
scenarioParameters.forEach(p -> runner.variable(p.getName(), p.getValue()));
}

runner.variable(EXECUTION_ID, executionId);
runner.name(String.format("Scenario(%s)", name));

injectAll(scenario, citrus, context);

try {
runner.start();
scenario.run(runner);
} finally {
runner.stop();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 the original author or authors.
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,48 +14,43 @@
* limitations under the License.
*/

package org.citrusframework.simulator.service.impl;
package org.citrusframework.simulator.service.runner;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import jakarta.annotation.Nullable;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.citrusframework.Citrus;
import org.citrusframework.annotations.CitrusAnnotations;
import org.citrusframework.context.TestContext;
import org.citrusframework.simulator.config.SimulatorConfigurationProperties;
import org.citrusframework.simulator.model.ScenarioExecution;
import org.citrusframework.simulator.model.ScenarioParameter;
import org.citrusframework.simulator.scenario.ScenarioRunner;
import org.citrusframework.simulator.scenario.SimulatorScenario;
import org.citrusframework.simulator.service.ScenarioExecutionService;
import org.citrusframework.simulator.service.ScenarioExecutorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
* Asynchronous Legay
* {@inheritDoc}
*/
@Service
public class DefaultScenarioExecutorServiceImpl implements ScenarioExecutorService {
@ConditionalOnProperty(name= "citrus.simulator.mode", havingValue = "async")
public class AsyncScenarioExecutorServiceImpl extends AbstractLegacyScenarioExecutorService implements ApplicationListener<ContextClosedEvent>, DisposableBean {

private static final Logger logger = LoggerFactory.getLogger( DefaultScenarioExecutorServiceImpl.class);
private static final Logger logger = LoggerFactory.getLogger( AsyncScenarioExecutorServiceImpl.class);

private final ApplicationContext applicationContext;
private final Citrus citrus;
private final ScenarioExecutionService scenarioExecutionService;

private final ExecutorService executorService;

public DefaultScenarioExecutorServiceImpl(ApplicationContext applicationContext, Citrus citrus, ScenarioExecutionService scenarioExecutionService, SimulatorConfigurationProperties properties) {
this.applicationContext = applicationContext;
this.citrus = citrus;
this.scenarioExecutionService = scenarioExecutionService;
public AsyncScenarioExecutorServiceImpl(ApplicationContext applicationContext, Citrus citrus, ScenarioExecutionService scenarioExecutionService, SimulatorConfigurationProperties properties) {
super(applicationContext, citrus,scenarioExecutionService);

this.executorService = Executors.newFixedThreadPool(
properties.getExecutorThreads(),
Expand All @@ -77,21 +72,9 @@ public void onApplicationEvent(ContextClosedEvent event) {
}

@Override
public final Long run(String name, @Nullable List<ScenarioParameter> scenarioParameters) {
return run(applicationContext.getBean(name, SimulatorScenario.class), name, scenarioParameters);
}

@Override
public final Long run(SimulatorScenario scenario, String name, @Nullable List<ScenarioParameter> scenarioParameters) {
protected void startScenario(Long executionId, String name, SimulatorScenario scenario, List<ScenarioParameter> scenarioParameters) {
logger.info("Starting scenario : {}", name);

ScenarioExecution scenarioExecution = scenarioExecutionService.createAndSaveExecutionScenario(name, scenarioParameters);

prepareBeforeExecution(scenario);

startScenarioAsync(scenarioExecution.getExecutionId(), name, scenario, scenarioParameters);

return scenarioExecution.getExecutionId();
startScenarioAsync(executionId, name, scenario, scenarioParameters);
}

private Future<?> startScenarioAsync(Long executionId, String name, SimulatorScenario scenario, List<ScenarioParameter> scenarioParameters) {
Expand All @@ -100,42 +83,14 @@ private Future<?> startScenarioAsync(Long executionId, String name, SimulatorSce

private void startScenarioSync(Long executionId, String name, SimulatorScenario scenario, List<ScenarioParameter> scenarioParameters) {
try {
TestContext context = citrus.getCitrusContext().createTestContext();
var context = createTestContext();
createAndRunScenarioRunner(context, executionId, name, scenario, scenarioParameters);
logger.debug("Scenario completed: {}", name);
} catch (Exception e) {
logger.error("Scenario completed with error: {}!", name, e);
}
}

private void createAndRunScenarioRunner(TestContext context, Long executionId, String name, SimulatorScenario scenario, List<ScenarioParameter> scenarioParameters) {
ScenarioRunner runner = new ScenarioRunner(scenario.getScenarioEndpoint(), applicationContext, context);
if (scenarioParameters != null) {
scenarioParameters.forEach(p -> runner.variable(p.getName(), p.getValue()));
}

runner.variable(ScenarioExecution.EXECUTION_ID, executionId);
runner.name(String.format("Scenario(%s)", name));

CitrusAnnotations.injectAll(scenario, citrus, context);

try {
runner.start();
scenario.run(runner);
} finally {
runner.stop();
}
}

/**
* Prepare scenario instance before execution. Subclasses can add custom preparation steps in
* here.
*
* @param scenario
*/
protected void prepareBeforeExecution(SimulatorScenario scenario) {
}

private void shutdownExecutor() {
logger.debug("Request to shutdown executor");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.citrusframework.simulator.service.runner;

public enum SimulatorMode {

ASYNC,
SYNC
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.citrusframework.simulator.service.runner;

import org.citrusframework.Citrus;
import org.citrusframework.simulator.config.SimulatorConfigurationProperties;
import org.citrusframework.simulator.model.ScenarioParameter;
import org.citrusframework.simulator.scenario.SimulatorScenario;
import org.citrusframework.simulator.service.ScenarioExecutionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import java.util.List;

/**
* {@inheritDoc}
*/
@Service
@ConditionalOnProperty(name= "citrus.simulator.mode", havingValue = "sync", matchIfMissing = true)
public class SyncScenarioExecutorServiceImpl extends AbstractLegacyScenarioExecutorService {

private static final Logger logger = LoggerFactory.getLogger( SyncScenarioExecutorServiceImpl.class);

public SyncScenarioExecutorServiceImpl(ApplicationContext applicationContext, Citrus citrus, ScenarioExecutionService scenarioExecutionService, SimulatorConfigurationProperties properties) {
super(applicationContext, citrus,scenarioExecutionService);
}

@Override
public final void startScenario(Long executionId, String name,SimulatorScenario scenario, List<ScenarioParameter> scenarioParameters) {
logger.info("Starting scenario : {}", name);
try {
var context = createTestContext();
createAndRunScenarioRunner(context, executionId, name, scenario, scenarioParameters);
logger.debug("Scenario completed: {}", name);
} catch (Exception e) {
logger.error("Scenario completed with error: {}!", name, e);
}
}

/**
* Prepare scenario instance before execution. Subclasses can add custom preparation steps in
* here.
*
* @param scenario
*/
protected void prepareBeforeExecution(SimulatorScenario scenario) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
"description": "Full qualified class name of additional Spring Java configuration to automatically load beans from.",
"defaultValue": "org.citrusframework.simulator.SimulatorConfig"
},
{
"name": "citrus.simulator.mode",
"type": "org.citrusframework.simulator.service.runner.SimulatorMode",
"description": "Execution mode of simulator.",
"defaultValue": "sync"
},
{
"name": "citrus.simulator.inbound.xml.dictionary.enabled",
"type": "java.lang.Boolean",
Expand Down
Loading

0 comments on commit 7da5a64

Please sign in to comment.