com.gh.bmd.jrt.android.service.RoutineService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jroutine-android Show documentation
Show all versions of jroutine-android Show documentation
Parallel programming on the go
/*
* 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 com.gh.bmd.jrt.android.service;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcelable;
import android.os.RemoteException;
import com.gh.bmd.jrt.android.invocation.ContextInvocation;
import com.gh.bmd.jrt.builder.RoutineConfiguration;
import com.gh.bmd.jrt.builder.RoutineConfiguration.Builder;
import com.gh.bmd.jrt.builder.RoutineConfiguration.OrderType;
import com.gh.bmd.jrt.channel.OutputConsumer;
import com.gh.bmd.jrt.channel.ParameterChannel;
import com.gh.bmd.jrt.common.InvocationException;
import com.gh.bmd.jrt.common.RoutineException;
import com.gh.bmd.jrt.core.AbstractRoutine;
import com.gh.bmd.jrt.invocation.Invocation;
import com.gh.bmd.jrt.log.Log;
import com.gh.bmd.jrt.log.Log.LogLevel;
import com.gh.bmd.jrt.log.Logger;
import com.gh.bmd.jrt.runner.Runner;
import com.gh.bmd.jrt.time.TimeDuration;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static com.gh.bmd.jrt.common.Reflection.findConstructor;
/**
* Basic implementation of a service running routine invocations.
*
* Created by davide on 1/9/15.
*/
public class RoutineService extends Service {
public static final int MSG_ABORT = -1;
public static final int MSG_COMPLETE = 2;
public static final int MSG_DATA = 1;
public static final int MSG_INIT = 0;
private static final String KEY_ABORT_EXCEPTION = "abort_exception";
private static final String KEY_AVAILABLE_TIMEOUT = "avail_time";
private static final String KEY_AVAILABLE_UNIT = "avail_unit";
private static final String KEY_CORE_INVOCATIONS = "max_retained";
private static final String KEY_DATA_VALUE = "data_value";
private static final String KEY_INPUT_ORDER = "input_order";
private static final String KEY_INVOCATION_ARGS = "invocation_args";
private static final String KEY_INVOCATION_CLASS = "invocation_class";
private static final String KEY_INVOCATION_ID = "invocation_id";
private static final String KEY_LOG_CLASS = "log_class";
private static final String KEY_LOG_LEVEL = "log_level";
private static final String KEY_MAX_INVOCATIONS = "max_running";
private static final String KEY_OUTPUT_ORDER = "output_order";
private static final String KEY_PARALLEL_INVOCATION = "parallel_invocation";
private static final String KEY_RUNNER_CLASS = "runner_class";
private final Messenger mInMessenger = new Messenger(new IncomingHandler(this));
private final HashMap mInvocationMap =
new HashMap();
private final Logger mLogger;
private final Object mMutex = new Object();
private final HashMap mRoutineMap =
new HashMap();
/**
* Constructor.
*/
@SuppressWarnings("unused")
public RoutineService() {
this(Logger.getGlobalLog(), Logger.getGlobalLogLevel());
}
/**
* Constructor.
*
* @param log the log instance.
* @param logLevel the log level.
*/
public RoutineService(@Nullable final Log log, @Nullable final LogLevel logLevel) {
mLogger = Logger.newLogger(log, logLevel, this);
}
/**
* Extracts the abort exception from the specified message.
*
* @param message the message.
* @return the exception or null.
* @throws java.lang.NullPointerException if the specified message is null.
*/
@Nullable
public static Throwable getAbortError(@Nonnull final Message message) {
final Bundle data = message.peekData();
if (data == null) {
return null;
}
data.setClassLoader(RoutineService.class.getClassLoader());
return (Throwable) data.getSerializable(KEY_ABORT_EXCEPTION);
}
/**
* Extracts the value object from the specified message.
*
* @param message the message.
* @return the value or null.
* @throws java.lang.NullPointerException if the specified message is null.
*/
@Nullable
public static Object getValue(@Nonnull final Message message) {
final Bundle data = message.peekData();
if (data == null) {
return null;
}
data.setClassLoader(RoutineService.class.getClassLoader());
final ParcelableValue parcelable = data.getParcelable(KEY_DATA_VALUE);
return parcelable.getValue();
}
/**
* Puts the specified asynchronous invocation info into the passed bundle.
*
* @param bundle the bundle to fill.
* @param invocationId the invocation ID.
* @param invocationClass the invocation class.
* @param invocationArgs the invocation constructor arguments.
* @param configuration the routine configuration.
* @param runnerClass the runner class.
* @param logClass the log class.
* @throws java.lang.NullPointerException if any of the specified non-null parameters is null.
*/
public static void putAsyncInvocation(@Nonnull final Bundle bundle,
@Nonnull final String invocationId,
@Nonnull final Class extends ContextInvocation, ?>> invocationClass,
@Nonnull final Object[] invocationArgs,
@Nonnull final RoutineConfiguration configuration,
@Nullable final Class extends Runner> runnerClass,
@Nullable final Class extends Log> logClass) {
putInvocation(bundle, false, invocationId, invocationClass, invocationArgs, configuration,
runnerClass, logClass);
}
/**
* Puts the specified abort exception into the passed bundle.
*
* @param bundle the bundle to fill.
* @param invocationId the invocation ID.
* @param error the exception instance.
*/
public static void putError(@Nonnull final Bundle bundle, @Nonnull final String invocationId,
@Nullable final Throwable error) {
putInvocationId(bundle, invocationId);
putError(bundle, error);
}
/**
* Puts the specified invocation ID into the passed bundle.
*
* @param bundle the bundle to fill.
* @param invocationId the invocation ID.
*/
public static void putInvocationId(@Nonnull final Bundle bundle,
@Nonnull final String invocationId) {
bundle.putString(KEY_INVOCATION_ID, invocationId);
}
/**
* Puts the specified parallel invocation info into the passed bundle.
*
* @param bundle the bundle to fill.
* @param invocationId the invocation ID.
* @param invocationClass the invocation class.
* @param invocationArgs the invocation constructor arguments.
* @param configuration the routine configuration.
* @param runnerClass the runner class.
* @param logClass the log class.
* @throws java.lang.NullPointerException if any of the specified non-null parameters is null.
*/
public static void putParallelInvocation(@Nonnull final Bundle bundle,
@Nonnull final String invocationId,
@Nonnull final Class extends ContextInvocation, ?>> invocationClass,
@Nonnull final Object[] invocationArgs,
@Nonnull final RoutineConfiguration configuration,
@Nullable final Class extends Runner> runnerClass,
@Nullable final Class extends Log> logClass) {
putInvocation(bundle, true, invocationId, invocationClass, invocationArgs, configuration,
runnerClass, logClass);
}
/**
* Puts the specified value object into the passed bundle.
*
* @param bundle the bundle to fill.
* @param invocationId the invocation ID.
* @param value the value instance.
*/
public static void putValue(@Nonnull final Bundle bundle, @Nonnull final String invocationId,
@Nullable final Object value) {
putInvocationId(bundle, invocationId);
putValue(bundle, value);
}
private static void putError(@Nonnull final Bundle bundle, @Nullable final Throwable error) {
bundle.putSerializable(RoutineService.KEY_ABORT_EXCEPTION, error);
}
private static void putInvocation(@Nonnull final Bundle bundle, boolean isParallel,
@Nonnull final String invocationId,
@Nonnull final Class extends ContextInvocation, ?>> invocationClass,
@Nonnull final Object[] invocationArgs,
@Nonnull final RoutineConfiguration configuration,
@Nullable final Class extends Runner> runnerClass,
@Nullable final Class extends Log> logClass) {
bundle.putBoolean(KEY_PARALLEL_INVOCATION, isParallel);
bundle.putString(KEY_INVOCATION_ID, invocationId);
bundle.putSerializable(KEY_INVOCATION_CLASS, invocationClass);
final int length = invocationArgs.length;
final ParcelableValue[] argValues = new ParcelableValue[length];
for (int i = 0; i < length; i++) {
argValues[i] = new ParcelableValue(invocationArgs[i]);
}
bundle.putParcelableArray(KEY_INVOCATION_ARGS, argValues);
bundle.putInt(KEY_CORE_INVOCATIONS,
configuration.getCoreInvocationsOr(RoutineConfiguration.DEFAULT));
bundle.putInt(KEY_MAX_INVOCATIONS,
configuration.getMaxInvocationsOr(RoutineConfiguration.DEFAULT));
final TimeDuration availTimeout = configuration.getAvailTimeoutOr(null);
if (availTimeout != null) {
bundle.putLong(KEY_AVAILABLE_TIMEOUT, availTimeout.time);
bundle.putSerializable(KEY_AVAILABLE_UNIT, availTimeout.unit);
}
bundle.putSerializable(KEY_INPUT_ORDER, configuration.getInputOrderOr(null));
bundle.putSerializable(KEY_OUTPUT_ORDER, configuration.getOutputOrderOr(null));
bundle.putSerializable(KEY_RUNNER_CLASS, runnerClass);
bundle.putSerializable(KEY_LOG_CLASS, logClass);
bundle.putSerializable(KEY_LOG_LEVEL, configuration.getLogLevelOr(null));
}
private static void putValue(@Nonnull final Bundle bundle, @Nullable final Object value) {
bundle.putParcelable(KEY_DATA_VALUE, new ParcelableValue(value));
}
@Override
public void onDestroy() {
synchronized (mMutex) {
for (final RoutineState routineState : mRoutineMap.values()) {
routineState.purge();
}
}
super.onDestroy();
}
@Override
public IBinder onBind(@Nonnull final Intent intent) {
return mInMessenger.getBinder();
}
@Nonnull
private RoutineInvocation getInvocation(@Nonnull final Message message) {
final Bundle data = message.peekData();
if (data == null) {
mLogger.err("the service message has no data");
throw new IllegalArgumentException(
"[" + getClass().getCanonicalName() + "] the service message has no data");
}
data.setClassLoader(getClassLoader());
final String invocationId = data.getString(KEY_INVOCATION_ID);
if (invocationId == null) {
mLogger.err("the service message has no invocation ID");
throw new IllegalArgumentException(
"[" + getClass().getCanonicalName() + "] the service message has no invocation"
+ " ID");
}
synchronized (mMutex) {
final RoutineInvocation invocation = mInvocationMap.get(invocationId);
if (invocation == null) {
mLogger.err("the service message has no invalid invocation ID: %d", invocationId);
throw new IllegalArgumentException(
"[" + getClass().getCanonicalName() + "] the service message has invalid "
+ "invocation ID: " + invocationId);
}
return invocation;
}
}
@SuppressWarnings("unchecked")
private void initRoutine(final Message message) {
final Bundle data = message.peekData();
if (data == null) {
mLogger.err("the service message has no data");
throw new IllegalArgumentException(
"[" + getClass().getCanonicalName() + "] the service message has no data");
}
data.setClassLoader(getClassLoader());
final String invocationId = data.getString(KEY_INVOCATION_ID);
if (invocationId == null) {
mLogger.err("the service message has no invocation ID");
throw new IllegalArgumentException(
"[" + getClass().getCanonicalName() + "] the service message has no invocation "
+ "ID");
}
final Class extends ContextInvocation