/*
* Copyright 2014, The Sporting Exchange Limited
*
* 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.betfair.cougar.transport.impl;
import com.betfair.cougar.api.DehydratedExecutionContext;
import com.betfair.cougar.api.ExecutionContext;
import com.betfair.cougar.core.api.ev.ExecutionVenue;
import com.betfair.cougar.core.api.ev.OperationDefinition;
import com.betfair.cougar.core.api.ev.OperationKey;
import com.betfair.cougar.core.api.exception.CougarException;
import com.betfair.cougar.core.api.exception.CougarFrameworkException;
import com.betfair.cougar.core.api.tracing.Tracer;
import com.betfair.cougar.transport.api.CommandResolver;
import com.betfair.cougar.transport.api.CommandValidator;
import com.betfair.cougar.transport.api.ExecutionCommand;
import com.betfair.cougar.transport.api.TransportCommand;
import com.betfair.cougar.transport.api.TransportCommandProcessor;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
/**
* Base implementation of TransportCommandProcessor.
* @param <T> The type of TransportCommand that the TransportHandler implementation can process
*/
public abstract class AbstractCommandProcessor<T extends TransportCommand> implements TransportCommandProcessor<T> {
private ExecutionVenue ev;
private Executor executor;
protected Tracer tracer;
private AtomicLong executionsProcessed = new AtomicLong();
private AtomicLong commandsProcessed = new AtomicLong();
private AtomicLong errorsWritten = new AtomicLong();
private AtomicLong ioErrorsEncountered = new AtomicLong();
/**
* Set the ExecutionVenue that will execute the resolved command
* @param ev
*/
public final void setExecutionVenue(ExecutionVenue ev) {
this.ev = ev;
}
/**
* Returns the ExecutionVenue that will execute resolved commands
* @return the ExecutionVenue
*/
public ExecutionVenue getExecutionVenue() {
return ev;
}
/**
*
* @param executor
*/
public void setExecutor(Executor executor) {
this.executor = executor;
}
protected Executor getExecutor() {
return executor;
}
/**
* Processes a TransportCommand.
* @param command
*/
public void process(final T command) {
boolean traceStarted = false;
incrementCommandsProcessed();
DehydratedExecutionContext ctx = null;
try {
validateCommand(command);
CommandResolver<T> resolver = createCommandResolver(command, tracer);
ctx = resolver.resolveExecutionContext();
List<ExecutionCommand> executionCommands = resolver.resolveExecutionCommands();
if (executionCommands.size() > 1) {
throw new CougarFrameworkException("Resolved >1 command in a non-batch call!");
}
ExecutionCommand exec = executionCommands.get(0);
tracer.start(ctx.getRequestUUID(), exec.getOperationKey());
traceStarted = true;
executeCommand(exec, ctx);
} catch(CougarException ce) {
executeError(command, ctx, ce, traceStarted);
} catch (Exception e) {
executeError(command, ctx, new CougarFrameworkException("Unexpected exception while processing transport command", e), traceStarted);
}
}
/**
* Get the list of command validators to be used to validate commands.
*/
protected abstract List<CommandValidator<T>> getCommandValidators();
/**
* Enables validation (and rejection) of processing of a command.
*/
protected void validateCommand(final T command) throws CougarException {
List<CommandValidator<T>> validators = getCommandValidators();
for (CommandValidator<T> v : validators) {
v.validate(command);
}
}
/**
* Execute the supplied command
* @param finalExec
* @param finalCtx
*/
protected void executeCommand(final ExecutionCommand finalExec, final ExecutionContext finalCtx) {
executionsProcessed.incrementAndGet();
ev.execute(finalCtx,
finalExec.getOperationKey(),
finalExec.getArgs(),
finalExec,
executor,
finalExec.getTimeConstraints());
}
protected void executeError(final T finalExec, final DehydratedExecutionContext finalCtx, final CougarException finalError, final boolean traceStarted) {
executor.execute(new Runnable() {
@Override
public void run() {
writeErrorResponse(finalExec, finalCtx, finalError, traceStarted);
}
});
}
/**
* Create an implementation that will resolve the supplied command to an operation key,
* arguments and a listener for callback
*
* @param command the command to resolve
* @param tracer
* @return the resolved operation, arguments and listener
*/
protected abstract CommandResolver<T> createCommandResolver(T command, Tracer tracer);
/**
* Write an exception back to the client.
* @param command the command that caused the error
* @param e the exception that was thrown
* @param traceStarted
*/
protected abstract void writeErrorResponse(T command, DehydratedExecutionContext context, CougarException e, boolean traceStarted);
protected final OperationDefinition getOperationDefinition(OperationKey key) {
return ev.getOperationDefinition(key);
}
public void setTracer(Tracer tracer) {
this.tracer = tracer;
}
/**
* Convenience abstract implementation of CommandResolver where only a single
* ExecutionRequest is to be resolved.
* @param <C> The type of Command that the CommandResolver implementation can resolve
*/
protected abstract class SingleExecutionCommandResolver<C extends TransportCommand> implements CommandResolver<C> {
private Tracer tracer;
public SingleExecutionCommandResolver(Tracer tracer) {
this.tracer = tracer;
}
public final List<ExecutionCommand> resolveExecutionCommands() {
ArrayList<ExecutionCommand> list = new ArrayList<ExecutionCommand>();
list.add(resolveExecutionCommand(tracer));
return list;
}
public abstract ExecutionCommand resolveExecutionCommand(Tracer tracer);
}
protected final void incrementIoErrorsEncountered() {
ioErrorsEncountered.incrementAndGet();
}
protected final void incrementCommandsProcessed() {
commandsProcessed.incrementAndGet();
}
protected final void incrementErrorsWritten() {
errorsWritten.incrementAndGet();
}
@ManagedAttribute
public long getExecutionsProcessed() {
return executionsProcessed.get();
}
@ManagedAttribute
public long getCommandsProcessed() {
return commandsProcessed.get();
}
@ManagedAttribute
public long getErrorsWritten() {
return errorsWritten.get();
}
@ManagedAttribute
public long getIoErrorsEncountered() {
return ioErrorsEncountered.get();
}
}