All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gh.bmd.jrt.android.routine.ServiceRoutine Maven / Gradle / Ivy

There is a newer version: 6.0.0
Show newest version
/*
 * 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.routine;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;

import com.gh.bmd.jrt.android.invocation.AndroidInvocation;
import com.gh.bmd.jrt.android.service.RoutineService;
import com.gh.bmd.jrt.builder.RoutineConfiguration;
import com.gh.bmd.jrt.builder.RoutineConfiguration.OrderType;
import com.gh.bmd.jrt.builder.RoutineConfiguration.TimeoutAction;
import com.gh.bmd.jrt.channel.OutputChannel;
import com.gh.bmd.jrt.channel.OutputConsumer;
import com.gh.bmd.jrt.channel.ParameterChannel;
import com.gh.bmd.jrt.channel.StandaloneChannel;
import com.gh.bmd.jrt.channel.StandaloneChannel.StandaloneInput;
import com.gh.bmd.jrt.channel.StandaloneChannel.StandaloneOutput;
import com.gh.bmd.jrt.common.InvocationException;
import com.gh.bmd.jrt.invocation.Invocation;
import com.gh.bmd.jrt.invocation.Invocations;
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.routine.Routine;
import com.gh.bmd.jrt.routine.TemplateRoutine;
import com.gh.bmd.jrt.runner.Runner;
import com.gh.bmd.jrt.time.TimeDuration;

import java.lang.reflect.Constructor;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import static com.gh.bmd.jrt.android.service.RoutineService.getAbortError;
import static com.gh.bmd.jrt.android.service.RoutineService.getValue;
import static com.gh.bmd.jrt.android.service.RoutineService.putAsyncInvocation;
import static com.gh.bmd.jrt.android.service.RoutineService.putError;
import static com.gh.bmd.jrt.android.service.RoutineService.putInvocationId;
import static com.gh.bmd.jrt.android.service.RoutineService.putParallelInvocation;
import static com.gh.bmd.jrt.android.service.RoutineService.putValue;
import static com.gh.bmd.jrt.builder.RoutineConfiguration.builder;
import static com.gh.bmd.jrt.common.Reflection.findConstructor;
import static java.util.UUID.randomUUID;

/**
 * Routine implementation employing an Android service to run its invocations.
 * 

* Created by davide on 1/8/15. * * @param the input data type. * @param the output data type. */ class ServiceRoutine extends TemplateRoutine { private final RoutineConfiguration mConfiguration; private final Context mContext; private final Class> mInvocationClass; private final Class mLogClass; private final Logger mLogger; private final Looper mLooper; private final Routine mRoutine; private final Class mRunnerClass; private final Class mServiceClass; /** * Constructor. * * @param context the routine context. * @param serviceClass the service class. * @param invocationClass the invocation class. * @param configuration the routine configuration. * @param looper the message looper. * @param runnerClass the asynchronous runner class. * @param logClass the log class. * @throws java.lang.IllegalArgumentException if at least one of the parameter is invalid. * @throws java.lang.NullPointerException if one of the parameters is null. */ ServiceRoutine(@Nonnull final Context context, @Nullable final Class serviceClass, @Nonnull final Class> invocationClass, @Nonnull final RoutineConfiguration configuration, @Nullable final Looper looper, @Nullable final Class runnerClass, @Nullable final Class logClass) { if (runnerClass != null) { findConstructor(runnerClass); } Log log = null; if (logClass != null) { final Constructor constructor = findConstructor(logClass); try { log = constructor.newInstance(); } catch (final Throwable t) { throw new IllegalArgumentException(t); } } if (log == null) { log = configuration.getLogOr(Logger.getGlobalLog()); } final Runner runner = configuration.getRunnerOr(null); mContext = context.getApplicationContext(); mLooper = looper; mServiceClass = (serviceClass != null) ? serviceClass : RoutineService.class; mInvocationClass = invocationClass; mConfiguration = configuration; mRunnerClass = (runnerClass != null) ? runnerClass : (runner != null) ? runner.getClass() : null; mLogClass = (logClass != null) ? logClass : log.getClass(); mLogger = Logger.newLogger(log, configuration.getLogLevelOr(Logger.getGlobalLogLevel()), this); mRoutine = JRoutine.on( Invocations.factoryOf((Class>) invocationClass)) .withConfiguration(configuration.builderFrom() .withInputSize(Integer.MAX_VALUE) .withInputTimeout(TimeDuration.ZERO) .withOutputSize(Integer.MAX_VALUE) .withOutputTimeout(TimeDuration.ZERO) .withLog(log) .buildConfiguration()) .buildRoutine(); final Logger logger = mLogger; logger.dbg("building service routine with configuration: %s", configuration); warn(logger, configuration); } /** * Logs any warning related to ignored options in the specified configuration. * * @param configuration the routine configuration. */ private static void warn(@Nonnull final Logger logger, @Nonnull final RoutineConfiguration configuration) { final int inputSize = configuration.getInputSizeOr(RoutineConfiguration.DEFAULT); if (inputSize != RoutineConfiguration.DEFAULT) { logger.wrn("the specified maximum input size will be ignored: %d", inputSize); } final TimeDuration inputTimeout = configuration.getInputTimeoutOr(null); if (inputTimeout != null) { logger.wrn("the specified input timeout will be ignored: %s", inputTimeout); } final int outputSize = configuration.getOutputSizeOr(RoutineConfiguration.DEFAULT); if (outputSize != RoutineConfiguration.DEFAULT) { logger.wrn("the specified maximum output size will be ignored: %d", outputSize); } final TimeDuration outputTimeout = configuration.getOutputTimeoutOr(null); if (outputTimeout != null) { logger.wrn("the specified output timeout will be ignored: %s", outputTimeout); } } @Nonnull public ParameterChannel invokeAsync() { return new ServiceChannel(false, mContext, mServiceClass, mInvocationClass, mConfiguration, mLooper, mRunnerClass, mLogClass, mLogger); } @Nonnull public ParameterChannel invokeParallel() { return new ServiceChannel(true, mContext, mServiceClass, mInvocationClass, mConfiguration, mLooper, mRunnerClass, mLogClass, mLogger); } @Nonnull public ParameterChannel invokeSync() { return mRoutine.invokeSync(); } @Override public void purge() { mRoutine.purge(); } /** * Service parameter channel implementation. * * @param the input data type. * @param the output data type. */ private static class ServiceChannel implements ParameterChannel { private final RoutineConfiguration mConfiguration; private final Context mContext; private final Messenger mInMessenger; private final Class> mInvocationClass; private final boolean mIsParallel; private final Class mLogClass; private final Logger mLogger; private final Object mMutex = new Object(); private final Class mRunnerClass; private final Class mServiceClass; private final StandaloneInput mStandaloneParamInput; private final StandaloneOutput mStandaloneParamOutput; private final StandaloneInput mStandaloneResultInput; private final StandaloneOutput mStandaloneResultOutput; private final String mUUID; private RoutineServiceConnection mConnection; private boolean mIsBound; private boolean mIsUnbound; private Messenger mOutMessenger; /** * Constructor. * * @param isParallel whether the invocation is parallel. * @param context the routine context. * @param serviceClass the service class. * @param invocationClass the invocation class. * @param configuration the routine configuration. * @param looper the message looper. * @param runnerClass the asynchronous runner class. * @param logClass the log class. * @param logger the routine logger. */ private ServiceChannel(boolean isParallel, @Nonnull final Context context, @Nonnull final Class serviceClass, @Nonnull Class> invocationClass, @Nonnull final RoutineConfiguration configuration, @Nullable final Looper looper, @Nullable final Class runnerClass, @Nullable final Class logClass, @Nonnull final Logger logger) { Looper handlerLooper = (looper != null) ? looper : Looper.myLooper(); if (handlerLooper == null) { handlerLooper = Looper.getMainLooper(); } mUUID = randomUUID().toString(); mIsParallel = isParallel; mContext = context; mInMessenger = new Messenger(new IncomingHandler(handlerLooper)); mServiceClass = serviceClass; mInvocationClass = invocationClass; mConfiguration = configuration; mRunnerClass = runnerClass; mLogClass = logClass; mLogger = logger; final Log log = logger.getLog(); final LogLevel logLevel = logger.getLogLevel(); final OrderType inputOrder = configuration.getInputOrderOr(null); final RoutineConfiguration inputConfiguration = builder().withOutputOrder(inputOrder) .withOutputSize( Integer.MAX_VALUE) .withOutputTimeout( TimeDuration.ZERO) .withLog(log) .withLogLevel(logLevel) .buildConfiguration(); final StandaloneChannel paramChannel = JRoutine.standalone().withConfiguration(inputConfiguration).buildChannel(); mStandaloneParamInput = paramChannel.input(); mStandaloneParamOutput = paramChannel.output(); final OrderType outputOrder = configuration.getOutputOrderOr(null); final TimeDuration readTimeout = configuration.getReadTimeoutOr(null); final TimeoutAction timeoutAction = configuration.getReadTimeoutActionOr(null); final RoutineConfiguration outputConfiguration = builder().withOutputOrder(outputOrder) .withOutputSize( Integer.MAX_VALUE) .withOutputTimeout( TimeDuration.ZERO) .withReadTimeout(readTimeout) .onReadTimeout(timeoutAction) .withLog(log) .withLogLevel(logLevel) .buildConfiguration(); final StandaloneChannel resultChannel = JRoutine.standalone().withConfiguration(outputConfiguration).buildChannel(); mStandaloneResultInput = resultChannel.input(); mStandaloneResultOutput = resultChannel.output(); } public boolean abort() { bindService(); return mStandaloneParamInput.abort(); } public boolean abort(@Nullable final Throwable reason) { bindService(); return mStandaloneParamInput.abort(reason); } public boolean isOpen() { return mStandaloneParamInput.isOpen(); } @Nonnull public ParameterChannel after(@Nonnull final TimeDuration delay) { mStandaloneParamInput.after(delay); return this; } @Nonnull public ParameterChannel after(final long delay, @Nonnull final TimeUnit timeUnit) { mStandaloneParamInput.after(delay, timeUnit); return this; } @Nonnull public ParameterChannel now() { mStandaloneParamInput.now(); return this; } @Nonnull public ParameterChannel pass( @Nullable final OutputChannel channel) { bindService(); mStandaloneParamInput.pass(channel); return this; } @Nonnull public ParameterChannel pass( @Nullable final Iterable inputs) { bindService(); mStandaloneParamInput.pass(inputs); return this; } @Nonnull public ParameterChannel pass(@Nullable final INPUT input) { bindService(); mStandaloneParamInput.pass(input); return this; } @Nonnull public ParameterChannel pass(@Nullable final INPUT... inputs) { bindService(); mStandaloneParamInput.pass(inputs); return this; } @Nonnull public OutputChannel result() { bindService(); mStandaloneParamInput.close(); return mStandaloneResultOutput; } private void bindService() { synchronized (mMutex) { if (mIsBound) { return; } mIsBound = true; final Context context = mContext; mConnection = new RoutineServiceConnection(); context.bindService(new Intent(context, mServiceClass), mConnection, Context.BIND_AUTO_CREATE); } } private void unbindService() { synchronized (mMutex) { if (mIsUnbound) { return; } mIsUnbound = true; mContext.unbindService(mConnection); } } /** * Output consumer sending messages to the service. */ private class ConnectionOutputConsumer implements OutputConsumer { public void onComplete() { final Message message = Message.obtain(null, RoutineService.MSG_COMPLETE); putInvocationId(message.getData(), mUUID); message.replyTo = mInMessenger; try { mOutMessenger.send(message); } catch (final RemoteException e) { unbindService(); throw new InvocationException(e); } } public void onError(@Nullable final Throwable error) { final Message message = Message.obtain(null, RoutineService.MSG_ABORT); putError(message.getData(), mUUID, error); message.replyTo = mInMessenger; try { mOutMessenger.send(message); } catch (final RemoteException e) { unbindService(); throw new InvocationException(e); } } public void onOutput(final INPUT input) { final Message message = Message.obtain(null, RoutineService.MSG_DATA); putValue(message.getData(), mUUID, input); message.replyTo = mInMessenger; try { mOutMessenger.send(message); } catch (final RemoteException e) { throw new InvocationException(e); } } } /** * Handler implementation managing incoming messages from the service. */ private class IncomingHandler extends Handler { /** * Constructor. * * @param looper the message looper. */ private IncomingHandler(@Nonnull final Looper looper) { super(looper); } @Override @SuppressWarnings("unchecked") public void handleMessage(@Nonnull final Message msg) { final Logger logger = mLogger; logger.dbg("incoming service message: %s", msg); try { switch (msg.what) { case RoutineService.MSG_DATA: mStandaloneResultInput.pass((OUTPUT) getValue(msg)); break; case RoutineService.MSG_COMPLETE: mStandaloneResultInput.close(); unbindService(); break; case RoutineService.MSG_ABORT: mStandaloneResultInput.abort(getAbortError(msg)); unbindService(); break; default: super.handleMessage(msg); } } catch (final Throwable t) { logger.err(t, "error while parsing service message"); final Message message = Message.obtain(null, RoutineService.MSG_ABORT); putError(message.getData(), mUUID, t); try { mOutMessenger.send(message); } catch (final RemoteException e) { logger.err(e, "error while sending service abort message"); } mStandaloneResultInput.abort(t); unbindService(); } } } /** * Service connection implementation managing the service communication state. */ private class RoutineServiceConnection implements ServiceConnection { private ConnectionOutputConsumer mConsumer; public void onServiceConnected(final ComponentName name, final IBinder service) { final Logger logger = mLogger; logger.dbg("service connected: %s", name); mOutMessenger = new Messenger(service); final Message message = Message.obtain(null, RoutineService.MSG_INIT); if (mIsParallel) { logger.dbg("sending parallel invocation message"); putParallelInvocation(message.getData(), mUUID, mInvocationClass, mConfiguration, mRunnerClass, mLogClass); } else { logger.dbg("sending async invocation message"); putAsyncInvocation(message.getData(), mUUID, mInvocationClass, mConfiguration, mRunnerClass, mLogClass); } message.replyTo = mInMessenger; try { mOutMessenger.send(message); mConsumer = new ConnectionOutputConsumer(); mStandaloneParamOutput.bind(mConsumer); } catch (final RemoteException e) { logger.err(e, "error while sending service invocation message"); mStandaloneResultInput.abort(e); unbindService(); } } public void onServiceDisconnected(final ComponentName name) { mLogger.dbg("service disconnected: %s", name); mStandaloneParamOutput.unbind(mConsumer); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy