/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.runtime.core.source.scheduler;
import static org.mule.runtime.api.message.Message.of;
import static org.mule.runtime.core.DefaultEventContext.create;
import static org.mule.runtime.core.api.Event.builder;
import static org.mule.runtime.core.api.Event.setCurrentEvent;
import static org.mule.runtime.core.config.i18n.CoreMessages.failedToScheduleWork;
import static org.mule.runtime.core.context.notification.ConnectorMessageNotification.MESSAGE_RECEIVED;
import static org.mule.runtime.core.internal.util.rx.Operators.requestUnbounded;
import static reactor.core.publisher.Mono.just;
import org.mule.runtime.api.exception.MuleException;
import org.mule.runtime.api.lifecycle.Disposable;
import org.mule.runtime.api.lifecycle.Initialisable;
import org.mule.runtime.api.lifecycle.InitialisationException;
import org.mule.runtime.api.message.Message;
import org.mule.runtime.api.meta.AbstractAnnotatedObject;
import org.mule.runtime.api.scheduler.Scheduler;
import org.mule.runtime.api.source.SchedulerMessageSource;
import org.mule.runtime.core.api.MuleContext;
import org.mule.runtime.core.api.construct.FlowConstruct;
import org.mule.runtime.core.api.construct.FlowConstructAware;
import org.mule.runtime.core.api.context.MuleContextAware;
import org.mule.runtime.api.lifecycle.CreateException;
import org.mule.runtime.core.api.processor.Processor;
import org.mule.runtime.core.api.source.MessageSource;
import org.mule.runtime.core.api.source.polling.PeriodicScheduler;
import org.mule.runtime.core.context.notification.ConnectorMessageNotification;
import org.mule.runtime.core.context.notification.NotificationHelper;
import java.util.concurrent.ScheduledFuture;
/**
* <p>
* Polling {@link org.mule.runtime.core.api.source.MessageSource}.
* </p>
* <p>
* The {@link DefaultSchedulerMessageSource} is responsible of creating a {@link org.mule.runtime.api.scheduler.Scheduler} at the
* initialization phase. This {@link org.mule.runtime.api.scheduler.Scheduler} can be stopped/started and executed by using the
* {@link org.mule.runtime.core.api.registry.MuleRegistry} interface, this way users can manipulate poll from outside mule server.
* </p>
*/
public class DefaultSchedulerMessageSource extends AbstractAnnotatedObject
implements MessageSource, FlowConstructAware, SchedulerMessageSource, MuleContextAware, Initialisable, Disposable {
private final PeriodicScheduler scheduler;
private final NotificationHelper notificationHelper;
private Scheduler pollingExecutor;
private ScheduledFuture<?> schedulingJob;
private Processor listener;
private FlowConstruct flowConstruct;
private MuleContext muleContext;
private boolean started;
/**
* @param muleContext application's context
* @param scheduler the scheduler
*/
public DefaultSchedulerMessageSource(MuleContext muleContext, PeriodicScheduler scheduler) {
this.muleContext = muleContext;
this.scheduler = scheduler;
this.notificationHelper =
new NotificationHelper(muleContext.getNotificationManager(), ConnectorMessageNotification.class, false);
}
@Override
public synchronized void start() throws MuleException {
if (started) {
return;
}
try {
// The initialization phase if handled by the scheduler
schedulingJob = scheduler.schedule(pollingExecutor, () -> run());
this.started = true;
} catch (Exception ex) {
this.stop();
throw new CreateException(failedToScheduleWork(), ex, this);
}
}
@Override
public synchronized void stop() throws MuleException {
if (!started) {
return;
}
// Stop the scheduler to address the case when the flow is stop but not the application
if (schedulingJob != null) {
schedulingJob.cancel(false);
schedulingJob = null;
}
this.started = false;
}
@Override
public void trigger() {
pollingExecutor.execute(() -> poll());
}
@Override
public boolean isStarted() {
return started;
}
/**
* Checks whether polling should take place on this instance.
*/
private final void run() {
// Make sure we start with a clean state.
setCurrentEvent(null);
if (!pollOnPrimaryInstanceOnly() || flowConstruct.getMuleContext().isPrimaryPollingInstance()) {
poll();
}
}
private boolean pollOnPrimaryInstanceOnly() {
return true;
}
/**
* Triggers the forced execution of the polling message processor ignoring the configured scheduler.
*/
private void poll() {
Message request = of(null);
pollWith(request);
}
private void pollWith(final Message request) {
try {
just(request)
.map(message -> builder(create(flowConstruct, getLocation())).message(request).flow(flowConstruct).build())
.doOnNext(event -> setCurrentEvent(event))
.doOnNext(event -> notificationHelper.fireNotification(this, event, flowConstruct, MESSAGE_RECEIVED))
.transform(listener)
.subscribe(requestUnbounded());
} catch (Exception e) {
muleContext.getExceptionListener().handleException(e);
}
}
/**
* <p>
* On the Initialization phase it.
* <ul>
* <li>Calls the {@link PeriodicScheduler} to create the scheduler</li>
* <li>Gets the Poll the message source</li>
* <li>Gets the Poll override</li>
* </ul>
* </p>
*/
@Override
public void initialise() throws InitialisationException {
createScheduler();
}
@Override
public void dispose() {
disposeScheduler();
}
private void createScheduler() throws InitialisationException {
pollingExecutor = muleContext.getSchedulerService().cpuLightScheduler();
}
private void disposeScheduler() {
if (pollingExecutor != null) {
pollingExecutor.stop();
pollingExecutor = null;
}
}
@Override
public void setFlowConstruct(FlowConstruct flowConstruct) {
this.flowConstruct = flowConstruct;
}
@Override
public void setMuleContext(MuleContext context) {
this.muleContext = context;
}
@Override
public void setListener(Processor listener) {
this.listener = listener;
}
}