/* * 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.core.impl.ev; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import com.betfair.cougar.api.ExecutionContext; import com.betfair.cougar.api.LogExtension; import com.betfair.cougar.api.LoggableEvent; import com.betfair.cougar.api.RequestContext; import com.betfair.cougar.api.RequestUUID; import com.betfair.cougar.api.geolocation.GeoLocationDetails; import com.betfair.cougar.api.security.IdentityChain; import com.betfair.cougar.core.api.RequestTimer; import com.betfair.cougar.core.api.ev.*; import com.betfair.cougar.core.api.exception.CougarException; import com.betfair.cougar.core.api.exception.CougarFrameworkException; import com.betfair.cougar.core.api.exception.CougarServiceException; import com.betfair.cougar.core.api.exception.ServerFaultCode; import com.betfair.cougar.core.api.logging.EventLogger; import com.betfair.cougar.core.api.tracing.Tracer; import com.betfair.cougar.core.impl.logging.RequestLogEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Resolves the operation key to a service Executable. The resolver will resolve the executable * from the resolvers that are registered with it, then return an Executable that ensures the wrapped * executable receives a RequestContext, enabling event logging and other server-side functionality * not available through the ExecutionContext interface. * */ public class ServiceExecutableResolver extends CompoundExecutableResolverImpl { private static final Logger LOGGER = LoggerFactory.getLogger(ServiceExecutableResolver.class); private EventLogger eventLogger; public void setEventLogger(EventLogger eventLogger) { this.eventLogger = eventLogger; } @Override public Executable resolveExecutable(OperationKey operationKey, ExecutionVenue ev) { if (!(ev instanceof ServiceRegisterableExecutionVenue)) { throw new IllegalStateException("I only support resolution from a service registerable EV"); } ServiceRegisterableExecutionVenue srev = (ServiceRegisterableExecutionVenue) ev; ServiceLogManager manager = srev.getServiceLogManager(operationKey.getNamespace(), operationKey.getServiceName(), operationKey.getVersion()); Executable executable = super.resolveExecutable(operationKey, ev); if (executable != null) { return new RequestContextExecutable(executable, manager, srev.getTracer()); } return null; } private class RequestContextExecutable implements ExecutableWrapper { private final Executable executable; private final ServiceLogManager manager; private Tracer tracer; public RequestContextExecutable(Executable executable, ServiceLogManager manager, Tracer tracer) { this.executable = executable; this.manager = manager; this.tracer = tracer; } @Override public void execute(final ExecutionContext ctx, final OperationKey key, final Object[] args, final ExecutionObserver observer, final ExecutionVenue executionVenue, final TimeConstraints timeConstraints) { final ExecutionContextAdapter ctxAdapter = new ExecutionContextAdapter(key, ctx, observer, manager, tracer); try { executable.execute(ctxAdapter, key, args, ctxAdapter, executionVenue, timeConstraints); } catch (CougarException e) { ctxAdapter.onResult(new ExecutionResult(e)); } catch (Exception e) { ctxAdapter.onResult(new ExecutionResult( new CougarServiceException(ServerFaultCode.ServiceRuntimeException, "Exception thrown by service method", e))); } } @Override public Executable getWrappedExecutable() { return executable; } @Override public <T extends Executable> T findChild(Class<T> clazz) { return ExecutableWrapperUtils.findChild(clazz, this); } } private class ExecutionContextAdapter implements RequestContext, ExecutionObserver { private final ExecutionObserver observer; private final OperationKey key; private final ExecutionContext original; private final ServiceLogManager manager; private RequestTimer timer = new RequestTimer(); private List<LoggableEvent> loggableEvents = new ArrayList<LoggableEvent>(); private LogExtension logExtension; private LogExtension connectedObjectLogExtension; private AtomicBoolean complete = new AtomicBoolean(false); private RequestContext originalRequestContext; private Tracer tracer; public ExecutionContextAdapter(final OperationKey key, final ExecutionContext original, final ExecutionObserver observer, ServiceLogManager manager, Tracer tracer) { this.key = key; this.original = original; this.tracer = tracer; if (original instanceof RequestContext) { this.originalRequestContext = (RequestContext) original; } this.observer = observer; this.manager = manager; } @Override public void onResult(ExecutionResult result) { if (key.getType() == OperationKey.Type.Request && !complete.getAndSet(true)) { timer.requestComplete(); switch (result.getResultType()) { case Fault: case Success: logEvents(result); break; } } if (key.getType() == OperationKey.Type.ConnectedObject && !complete.getAndSet(true)) { timer.requestComplete(); validateConnectedObjectLogExtension(result); } observer.onResult(result); } private void validateConnectedObjectLogExtension(ExecutionResult executionResult) { // need to validate the connected object extension, even though the logging's not done here but in the transport. a bit yucky if (connectedObjectLogExtension == null) { if (manager.getConnectedObjectLogExtensionClass() != null) { if (executionResult.getResult() instanceof ConnectedResponse) { throw new CougarFrameworkException("Connected object log extension expected but not found for " + key); } } } else { if (manager.getNumConnectedObjectLogExtensionFields() != connectedObjectLogExtension.getFieldsToLog().length) { throw new CougarFrameworkException("Connected object log extension class defined "+ connectedObjectLogExtension.getFieldsToLog().length+" fields. Expected" + manager.getNumConnectedObjectLogExtensionFields()); } } } @Override public GeoLocationDetails getLocation() { return original.getLocation(); } @Override public Date getReceivedTime() { return original.getReceivedTime(); } @Override public Date getRequestTime() { return original.getRequestTime(); } @Override public RequestUUID getRequestUUID() { return original.getRequestUUID(); } @Override public boolean traceLoggingEnabled() { return original.traceLoggingEnabled(); } @Override public int getTransportSecurityStrengthFactor() { return original.getTransportSecurityStrengthFactor(); } @Override public boolean isTransportSecure() { return original.isTransportSecure(); } @Override public void addEventLogRecord(LoggableEvent record) { if (originalRequestContext != null) { originalRequestContext.addEventLogRecord(record); } loggableEvents.add(record); } @Override public void setRequestLogExtension(LogExtension extension) { if (originalRequestContext != null) { originalRequestContext.setRequestLogExtension(extension); } this.logExtension = extension; } @Override public void setConnectedObjectLogExtension(LogExtension extension) { if (originalRequestContext != null) { originalRequestContext.setConnectedObjectLogExtension(extension); } this.connectedObjectLogExtension = extension; } public LogExtension getConnectedObjectLogExtension() { if (connectedObjectLogExtension != null) { return connectedObjectLogExtension; } if (originalRequestContext != null) { return originalRequestContext.getConnectedObjectLogExtension(); } return null; } @Override public void trace(String msg, Object... args) { tracer.trace(this, msg, args); } @Override public IdentityChain getIdentity() { return original.getIdentity(); } public String toString() { StringBuilder sb = new StringBuilder("ExecutionContextAdaptor:"); sb.append("geoLocationDetails=").append(getLocation()).append("|"); sb.append("identityChain=").append(getIdentity()).append("|"); sb.append("requestUUID=").append(getRequestUUID()).append("|"); sb.append("receivedTime=").append(getReceivedTime()).append("|"); sb.append("traceLoggingEnabled=").append(traceLoggingEnabled()).append("|"); sb.append("requestLogExtension=").append(logExtension); sb.append("connectedObjectLogExtension=").append(connectedObjectLogExtension); return sb.toString(); } private void logEvents(ExecutionResult executionResult) { String faultCode = ""; if (executionResult.isFault() && executionResult.getFault().getFault() != null) { faultCode = executionResult.getFault().getFault().getErrorCode(); } // now for the request logging Object [] fieldsToLog = null; if (logExtension == null) { if (manager.getLogExtensionClass() != null) { if (executionResult.isFault()) { // As it was a fault, the service cannot have guaranteed to add the extension // record, so we dummy one up with the correct number of fields fieldsToLog = new Object[manager.getNumLogExtensionFields()]; } else { throw new CougarFrameworkException("Log extension expected but not found for " + key); } } } else { if (manager.getNumLogExtensionFields() != logExtension.getFieldsToLog().length) { throw new CougarFrameworkException("Log extension class defined "+ logExtension.getFieldsToLog().length+" fields. Expected" + manager.getNumLogExtensionFields()); } fieldsToLog = logExtension.getFieldsToLog(); } RequestLogEvent operationEvent = new RequestLogEvent( manager.getLoggerName(), faultCode, timer.getReceivedTime(), key, original.getRequestUUID(), timer.getProcessTimeNanos()); eventLogger.logEvent(operationEvent, fieldsToLog); for (LoggableEvent event : loggableEvents) { eventLogger.logEvent(event, null); } } } }