Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
* This software is in the public domain under CC0 1.0 Universal plus a
* Grant of Patent License.
*
*
* To the extent possible under law, the author(s) have dedicated all
* copyright and related and neighboring rights to this software to the
* public domain worldwide. This software is distributed without any
* warranty.
*
*
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software (see the LICENSE.md file). If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
Expand Down Expand Up @@ -64,6 +64,10 @@ class ServiceFacadeImpl implements ServiceFacade {

/** Distributed ExecutorService for async services, etc */
protected ExecutorService distributedExecutorService = null
/** An executor for scheduled services */
protected CustomScheduledExecutor scheduledExecutor = null
/** Map of all serviceCallScheduled scheduledFuture (not concelled yet), using taskName as the key. */
protected final ConcurrentMap<String, ScheduledFuture<?>> scheduledFutureMap = new ConcurrentHashMap<>()

protected final ConcurrentMap<String, List<ServiceCallback>> callbackRegistry = new ConcurrentHashMap<>()

Expand All @@ -84,6 +88,7 @@ class ServiceFacadeImpl implements ServiceFacade {
restApi = new RestApi(ecfi)

jobWorkerPool = makeWorkerPool()
scheduledExecutor = makeScheduledExecutor()
}

private ThreadPoolExecutor makeWorkerPool() {
Expand All @@ -106,6 +111,16 @@ class ServiceFacadeImpl implements ServiceFacade {
workQueue, new ContextJavaUtil.JobThreadFactory())
}

private CustomScheduledExecutor makeScheduledExecutor() {
MNode serviceFacadeNode = ecfi.confXmlRoot.first("service-facade")

int coreSize = (serviceFacadeNode.attribute("scheduled-thread-pool-core") ?: "16") as int
int maxSize = (serviceFacadeNode.attribute("scheduled-thread-pool-max") ?: "32") as int
CustomScheduledExecutor executor = new CustomScheduledExecutor(coreSize)
executor.setMaximumPoolSize(maxSize)
return executor
}

void postFacadeInit() {
// load Service ECA rules
loadSecaRulesAll()
Expand Down Expand Up @@ -164,12 +179,25 @@ class ServiceFacadeImpl implements ServiceFacade {
void destroy() {
// destroy all service runners
for (ServiceRunner sr in serviceRunners.values()) sr.destroy()

// shutdown scheduled executor
try {
logger.info("Shutting scheduled executor")
scheduledExecutor.shutdown()
scheduledExecutor.awaitTermination(30, TimeUnit.SECONDS)
if (scheduledExecutor.isTerminated()) logger.info("Scheduled executor shut down and terminated")
else logger.warn("Scheduled executor not yet terminated, waited 30 seconds")
} catch (Throwable t) {
logger.error("Error in scheduledExecutor shutdown", t)
}
}

ServiceRunner getServiceRunner(String type) { serviceRunners.get(type) }
// NOTE: this is used in the ServiceJobList screen
ScheduledJobRunner getJobRunner() { jobRunner }

long getJobRunnerRate() { jobRunnerRate }

boolean isServiceDefined(String serviceName) {
ServiceDefinition sd = getServiceDefinition(serviceName)
if (sd != null) return true
Expand Down Expand Up @@ -568,11 +596,72 @@ class ServiceFacadeImpl implements ServiceFacade {
for (EmailEcaRule eer in emecaRuleList) eer.runIfMatches(message, emailServerId, eci)
}

/**
* True if a task with this name is registered.
*
* @param taskName Unique task name.
* @return true if a name is registerd
*/
boolean hasScheduledFuture(String taskName) {
return taskName && scheduledFutureMap.containsKey(taskName)
}

/** Get the scheduled future for a scheduled service call by taskName.
* @param taskName Unique task name with which the specified scheduled future is associated.
* @return scheduledFuture - scheduledFuture associated with the specified task name.
*/
ScheduledFuture<?> getScheduledFuture(String taskName) {
return (ScheduledFuture) scheduledFutureMap.get(taskName)
}

/** Associates the specified scheduled future for a scheduled service call with the specified taskName.
* @param taskName Unique task name with which the specified scheduled future is to be associated.
* @param scheduledFuture - scheduledFuture to be associated with the specified task name.
*/
ScheduledFuture<?> putScheduledFuture(String taskName, ScheduledFuture<?> scheduledFuture) {
if (taskName == null) throw new IllegalArgumentException("The argument taskName is null.")
if (scheduledFuture == null) throw new IllegalArgumentException("The argument scheduledFuture is null.")
return scheduledFutureMap.put(taskName, scheduledFuture)
}

/** If the specified taskName is not already associated with a scheduled future associates it
* with the given instance and returns null, else returns the current scheduled future.
* @param taskName Unique task name with which the specified scheduled future is to be associated.
* @param scheduledFuture - scheduledFuture to be associated with the specified task name.
*/
ScheduledFuture<?> putScheduledFutureIfAbsent(String taskName, ScheduledFuture<?> scheduledFuture) {
if (taskName == null) throw new IllegalArgumentException("The argument taskName is null.")
if (scheduledFuture == null) throw new IllegalArgumentException("The argument scheduledFuture is null.")
return (ScheduledFuture<?>) scheduledFutureMap.putIfAbsent(taskName, scheduledFuture)
}

/** Removes the entry for the scheduled future and the associated taskName if it is present.
* @param taskName Unique task name for which the specified scheduled future is to be removed.
*/
boolean removeScheduledFuture(String taskName) {
if (taskName == null) throw new IllegalArgumentException("The argument taskName is null.")
return scheduledFutureMap.remove(taskName)
}

/** Snapshot of current scheduled future task names. */
List<String> listScheduledFutureTaskNames() {
return new ArrayList<>(scheduledFutureMap.keySet())
}

/** Current scheduled future count. */
int scheduledFutureCount() {
return scheduledFutureMap.size()
}

@Override
ServiceCallSync sync() { return new ServiceCallSyncImpl(this) }
@Override
ServiceCallAsync async() { return new ServiceCallAsyncImpl(this) }
@Override
ServiceCallScheduled schedule(String taskName) { return new ServiceCallScheduledImpl(this, taskName) }
@Override
ServiceCallScheduled schedule() { return new ServiceCallScheduledImpl(this) }
@Override
ServiceCallJob job(String jobName) { return new ServiceCallJobImpl(jobName, this) }

@Override
Expand Down
109 changes: 109 additions & 0 deletions framework/src/main/java/org/moqui/service/ServiceCallScheduled.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* This software is in the public domain under CC0 1.0 Universal plus a
* Grant of Patent License.
*
* To the extent possible under law, the author(s) have dedicated all
* copyright and related and neighboring rights to this software to the
* public domain worldwide. This software is distributed without any
* warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software (see the LICENSE.md file). If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
package org.moqui.service;

import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;


/** Example use:
* ec.service.schedule().name("...").parameters([...]).initialDelay(50).atFixedRate(100).call()
* ec.service.schedule().name("...").parameters([...]).initialDelay(50).callFuture()
* ec.service.schedule().name("....").getScheduledFuture(urn).shutdown()
*/
@SuppressWarnings("unused")
public interface ServiceCallScheduled extends ServiceCall {
/** Name of the service to run. The combined service name, like: "${path}.${verb}${noun}". To explicitly separate
* the verb and noun put a hash (#) between them, like: "${path}.${verb}#${noun}" (this is useful for calling the
* implicit entity CrUD services where verb is create, update, or delete and noun is the name of the entity).
*/
ServiceCallScheduled name(String serviceName);

ServiceCallScheduled name(String verb, String noun);

ServiceCallScheduled name(String path, String verb, String noun);

/** Map of name, value pairs that make up the context (in parameters) passed to the service. */
ServiceCallScheduled parameters(Map<String, Object> context);

/** Single name, value pairs to put in the context (in parameters) passed to the service. */
ServiceCallScheduled parameter(String name, Object value);

/** Returns the name of the task for the scheduled service. */
String getTaskName();

/** Set a unique task name for the scheduled service. */
ServiceCallScheduled taskName(String taskName);

/** The time unit for all time parameters. Defaults to milliseconds. */
ServiceCallScheduled timeUnit(TimeUnit unit);

/** The service execution becomes enabled after the given initial delay. Defaults to 0. */
ServiceCallScheduled initialDelay(long delay) throws IllegalArgumentException;

/** The maximum time to wait before timeout for each service call. Override the transaction-timeout attribute in the service definition. */
ServiceCallScheduled transactionTimeout(int transactionTimeout);

/** Creates a periodic service that becomes enabled first after the given initial delay, and subsequently with the given period; that is executions will commence after initialDelay then initialDelay+period, then initialDelay + 2 * period, and so on. */
ServiceCallScheduled atFixedRate(long period) throws IllegalArgumentException;

/** Creates a periodic service that becomes enabled first after the given initial delay, and subsequently with the given delay between the termination of one execution and the commencement of the next. */
ServiceCallScheduled withFixedDelay(long delay) throws IllegalArgumentException;

/** The duration of the execution of the periodic task. After this duration the cyclical task will be canceled. */
ServiceCallScheduled duration(long duration);

/**
* Submits the service for the scheduled execution, ignoring the result.
* This effectively calls the service through a java.lang.Runnable implementation.
*/
ServiceCallScheduled call() throws ServiceException;

/**
* Submits the service for the scheduled execution and get a java.util.concurrent.ScheduledFuture object back so you can wait for the service to
* complete and get the result.
*
* This effectively calls the service through a java.util.concurrent.Callable implementation.
*/
ScheduledFuture<Map<String, Object>> callFuture() throws ServiceException;

/** Attempts to cancel the scheduled execution of this service. */
boolean cancel(boolean mayInterruptIfRunning);

/** Attempts to cancel the scheduled execution of this service after the specified delay.
* Returns the SheduledFuture of the cancel task.
*/
<V> ScheduledFuture<V> cancel(boolean mayInterruptIfRunning, long cancelDelay, TimeUnit unit);

/** Attempts to cancel the scheduled execution of this service after the specified delay.
* Returns the SheduledFuture of the cancel task.
*/
<V> ScheduledFuture<V> cancel(boolean mayInterruptIfRunning, long cancelDelay);

/** Returns true if this scheduled service was cancelled before it completed normally. */
boolean isCancelled();

/** Returns true if this scheduled service completed. Completion may be due to normal termination, an exception, or cancellation -- in all of these cases, this method will return true. */
boolean isDone();

/** Returns true if this scheduled service has not completed. */
boolean isRunning();

/** Get a Runnable object to do this service call through an ExecutorService or other runner of your choice. */
Runnable getRunnable();
/** Get a Callable object to do this service call through an ExecutorService of your choice. */
Callable<Map<String, Object>> getCallable();
}
11 changes: 9 additions & 2 deletions framework/src/main/java/org/moqui/service/ServiceFacade.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
* This software is in the public domain under CC0 1.0 Universal plus a
* Grant of Patent License.
*
*
* To the extent possible under law, the author(s) have dedicated all
* copyright and related and neighboring rights to this software to the
* public domain worldwide. This software is distributed without any
* warranty.
*
*
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software (see the LICENSE.md file). If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
Expand All @@ -16,6 +16,7 @@
import org.moqui.util.RestClient;

import java.util.Map;
import java.util.concurrent.ScheduledFuture;

/** ServiceFacade Interface */
@SuppressWarnings("unused")
Expand All @@ -27,6 +28,12 @@ public interface ServiceFacade {
/** Get a service caller to call a service asynchronously. */
ServiceCallAsync async();

/** Get a service caller to schedule a service call. */
ServiceCallScheduled schedule();

/** Get a service caller to schedule a service, assigning a specific taskName. */
ServiceCallScheduled schedule(String taskName);

/**
* Get a service caller to call a service job.
*
Expand Down
Loading