I have a Spring boot application with several camel routes that should start based on a quartz2's CronTrigger. For some reason, only the the route scheduled first is ever started, but it starts at the time scheduled for the last route.
route one: mytime - 1h
route two: mytime
Only route one is started, at mytime.
I have made a minimal example. Because my routes are supposed to check the contents of a database table and export part of it, in my example the routes will check the table and log the most recent date found in a column set in the properties.
Routebuilder:
/**
* Starts a list of routes that have been scheduled in application.yml
*/
#Component
public class ScheduledRoutesRouteBuilder extends SpringRouteBuilder {
private static final Logger LOG = LoggerFactory.getLogger(ScheduledRoutesRouteBuilder.class);
private static final String BEAN_CHECKDB = "bean:checkDBBean?method=getFirstRecord(%s, %s)";
#Autowired
private RoutesDefinition routesDefinition;
#Override
public void configure() throws Exception {
routesDefinition.getScheduledRoutes().stream()
.forEach(route -> createScheduledRoute(route));
}
private void createScheduledRoute(RouteDefinition aRoute) {
from(aRoute.getSchedule())
.routeId(aRoute.getRouteId())
.log(LoggingLevel.INFO, LOG, "Kickstarting export route: " + aRoute.getRouteId() + " - schedule: " + aRoute.getSchedule())
.to(String.format(BEAN_CHECKDB, aRoute.getDbTableName(), aRoute.getReferenceDateColumnName()));
System.out.println("Configured export route: " + aRoute.getRouteId() + " - schedule: " + aRoute.getSchedule());
}
}
application.yml:
# Schedules
scheduleFirst: 0 39 * * * ?
scheduleSecond: 0 41 * * * ?
scheduledRoutes:
- routeId: MonthProcessingRoute
dbTableName: month
referenceDateColumnName: acceptatiedatum
schedule: quartz2://CronTrigger?cron=${scheduleFirst}
- routeId: WeekProcessingRoute
dbTableName: week
referenceDateColumnName: acceptatiedatum
schedule: quartz2://CronTrigger?cron=${scheduleSecond}
Log:
Configured export route: MonthProcessingRoute - schedule:
quartz2://CronTrigger?cron=0 39 * * * ?
Configured export route: WeekProcessingRoute - schedule: quartz2://CronTrigger?cron=0 41 * * * ?
2018-03-20 05:37:33 INFO tryouts-spring-camel ivana.StartUp - -
- Started StartUp in 2.507 seconds (JVM running for 3.238)
2018-03-20 05:41:00 INFO tryouts-spring-camel ivana.routebuilders.ScheduledRoutesRouteBuilder - - - Kickstarting
export route: MonthProcessingRoute - schedule:
quartz2://CronTrigger?cron=0 39 * * * ?
Most recent date found in database table month: 2017-11-05 15:31:00.0
You should make sure to use unique triggerName/groupName for each of your Camel routes. It looks like you use the same name CronTrigger in both routes. Change that to be unique names, and it should work.
Related
I'm using SpringCloudGateway, I have an doubt on config parameter IO_SELECT_COUNT
/**
* Default selector thread count, fallback to -1 (no selector thread)
*/
public static final String IO_SELECT_COUNT = "reactor.netty.ioSelectCount";
Is this the thread count of netty boss eventloop group? why could this be -1 by default?
And is there any suggestion for the configuration of this parameter IO_SELECT_COUNT ?
I have implemented a scheduler with shedlock in my current spring project as follows:
#Scheduled(cron = "0 0/1 * * * *")
#SchedulerLock(name = "syncData",
lockAtMostFor = "${shedlock.myScheduler.lockAtMostFor}",
lockAtLeastFor = "${shedlock.myScheduler.lockAtLeastFor}")
public void syncSAData() {
//To assert that the lock is held
LockAssert.assertLocked();
...
}
Now I would like to write unit test for this function. Here the problem I am facing is: I am unable to mock first statement: LockAssert.assertLocked().
This should do the trick LockAssert.TestHelper.makeAllAssertsPass(true);
Just add it at the beginning of the test method.
Docs: https://github.com/lukas-krecan/ShedLock#lock-assert
What is good way to register adjacent HTTP requests with the Spring integration flow?
My application is:
For the every customer (has it's own flow, which start is scheduled by the poller)
GET operation 1 in the source application and the result is JSON_1
POST JSON_1 to the remote system and the result is JSON_1B
POST JSON_1B to the source application and the result is JSON_1C
GET operation 2 in the source application and the result is JSON_2
POST JSON_2 to the remote system and the result is JSON_2B
POST JSON_2B to the source application and the result is JSON_2C
...
GET operation n in the source application and the result is JSON_N
POST JSON_N to the remote system and the result is JSON_NB
POST JSON_NB to the source application and the result is JSON_NC
The operations 1, 2, ..., n must be in the order
Here is my example program (for the simplicity all the REST calls are for the same class)
#Configuration
public class ApplicationConfiguration {
#Autowired
private IntegrationFlowContext flowContext;
#Bean
public MethodInvokingMessageSource integerMessageSource() {
final MethodInvokingMessageSource source = new MethodInvokingMessageSource();
source.setObject(new AtomicInteger());
source.setMethodName("getAndIncrement");
return source;
}
#PostConstruct
public void createAndRegisterFlows() {
IntegrationFlowBuilder integrationFlowBuilder = createFlowBuilder();
for (int i = 1; i <= 2; i++) {
integrationFlowBuilder = flowBuilder(integrationFlowBuilder, i);
}
integrationFlowBuilder.handle(CharacterStreamWritingMessageHandler.stdout());
flowContext.registration(integrationFlowBuilder.get()).register();
}
private IntegrationFlowBuilder createFlowBuilder() {
return IntegrationFlows.from(this.integerMessageSource(), c -> c.poller(Pollers.fixedRate(5000)));
}
private IntegrationFlowBuilder flowBuilder(final IntegrationFlowBuilder integrationFlowBuilder, final int number) {
return integrationFlowBuilder
.handle(Http.outboundGateway("http://localhost:8055/greeting" + number).httpMethod(HttpMethod.GET)
.expectedResponseType(String.class))
.channel("getReceive" + number)
.handle(Http.outboundGateway("http://localhost:8055/greeting" + number).httpMethod(HttpMethod.POST)
.expectedResponseType(String.class))
.channel("postReceive" + number)
.handle(Http.outboundGateway("http://localhost:8055/greeting-final" + number)
.httpMethod(HttpMethod.POST).expectedResponseType(String.class))
.channel("postReceiveFinal" + number);
}
}
This program runs the integration flow
GET http://localhost:8055/greeting1
POST http://localhost:8055/greeting1 (previous result as an input)
POST http://localhost:8055/greeting-final1 (previous result as an input)
GET http://localhost:8055/greeting2
POST http://localhost:8055/greeting2 (previous result as an input)
POST http://localhost:8055/greeting-final2 (previous result as an input)
This is working as expected. But I'm wondering is this good way to do this, because the response from the call POST http://localhost:8055/greeting-final1 is not used in the call GET http://localhost:8055/greeting2. I only want that these calls are in this order.
Actually you have everything you needed with that loop to create to sub-flow calls to the REST services. Only what you are missing is a payload as a result from the greeting-final1 which is going to be published with the message to the .channel("postReceiveFinal" + number). The second iteration will subscribe the greeting2" to that channel and the payload is available for processing here. Not sure how to rework your flowBuilder() method, but you just need to use a payload from the message for whatever is your requirements, e.g. you can use it as an:
/**
* Specify an {#link Expression} to evaluate a value for the uri template variable.
* #param variable the uri template variable.
* #param expression the expression to evaluate value for te uri template variable.
* #return the current Spec.
* #see AbstractHttpRequestExecutingMessageHandler#setUriVariableExpressions(Map)
* #see ValueExpression
* #see org.springframework.expression.common.LiteralExpression
*/
public S uriVariable(String variable, Expression expression) {
to inject a payload to some request param since it is just HttpMethod.GET:
handle(Http.outboundGateway("http://localhost:8055/greeting2?param1={param1}")
.httpMethod(HttpMethod.GET)
.uriVariable("param1", "payload")
.expectedResponseType(String.class))
in our system we use a settings class to point to the properties file, depends on which eve it loads different property file. To access specific property, we call 'Settings.getString('property_name_here')'.
In my code, i loaded the #scheduled cron expression to a variable and try to passed to the #scheduled annotation, but it won't work,
here is my code:
in properties file:
cron.second=0
cron.min=1
cron.hour=14
in constructor i have:
this.cronExpression = new StringBuilder()
.append(settings.getString("cron.second"))
.append(" ")
.append(settings.getString("cron.min"))
.append(" ")
.append(settings.getString("cron.hour"))
.append(" ")
.append("*").append(" ").append("*").append(" ").append("*")
.toString();
which create a String of "0 1 14 * * *", it's a valid con expression
in the scheduled task i have:
#Scheduled(cron = "${this.cronExpression}")
public void scheduleTask() throws Exception {
....
}
when I ran the code it complaint:
Caused by: java.lang.IllegalStateException: Encountered invalid #Scheduled method 'scheduleTask': Cron expression must consist of 6 fields (found 1 in "${this.cronExpression}")
then I changed this.cronExpression to a list of String:
this.cronExpression = Lists.newArrayList();
this.cronExpression.add(settings.getString("cron.second"));
this.cronExpression.add(settings.getString("cron.min"));
this.cronExpression.add(settings.getString("cron.hour"));
this.cronExpression.add("*");
this.cronExpression.add("*");
this.cronExpression.add("*");
but still got the same error, so what exactly is the cron expression supposed to be?
What I did and it worked for me:
In properties file:
my.cron.expression=0 3 * * * ?
In code:
#Scheduled(cron = "${my.cron.expression}")
public void scheduleTask(){
...
}
I have Laravel setup so Monolog logs to syslog. Here is whats' in my start/global.php:
$monolog = Log::getMonolog();
$monolog->pushHandler(new Monolog\Handler\SyslogHandler('mywebsitename'));
This works well, except all log messages are dumped into /var/log/messages.
How can I specify a specific log file instead of messages? I've seen something called custom "channels" with Monolog. Is it something to do with that?
Also, how do I make sure that this log file is rotated like the rest of my log files?
I'm using Laravel 4.2, and in my setLog function I've added the RotatingFileHandler. This will solve both problems for you.
This code creates a file called mttParser-{Y-m-d}.log in app/storage/logs/import/, where {Y-m-d} is converted to todays date. The second parameter to the RotatingFileHandler, 10, sets to keep 10 versions of the log file before deleting them.
The IntrospectionProcessor simply adds the method, class, line number, where the log was called.
trait LogTrait
{
/**
* #var string The location within the storage path to put the log files
*/
protected $logFileLocation = 'logs/import/';
protected $logFileName = 'mttParser.log';
/**
* #var Logger
*/
protected $log;
/**
* Returns a valid log file, re-using an existing one if possible
*
* #return \Monolog\Logger
*/
public function getLog()
{
// Set a log file
if (!isset( $this->log )) {
$this->setLog();
}
// If it's been set somewhere else, re-create it
if ('Monolog\Logger' !== get_class( $this->log )) {
$this->setLog();
}
return $this->log;
}
/**
* Sets the log file
*/
public function setLog()
{
$this->log = new Logger( 'mttParserLog' );
// Make sure our directory exists
if (!File::isDirectory( storage_path( $this->logFileLocation ) )) {
File::makeDirectory( storage_path( $this->logFileLocation ) );
}
$this->log->pushHandler( new RotatingFileHandler( storage_path( $this->logFileLocation . $this->logFileName ),
10 ) );
$this->log->pushProcessor( new IntrospectionProcessor() );
}
}