/*
* Copyright 2014, The Sporting Exchange Limited
* Copyright 2014, Simon Matić Langford
*
* 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.*;
import com.betfair.cougar.api.*;
import com.betfair.cougar.core.api.ServiceDefinition;
import com.betfair.cougar.core.api.ServiceRegistrar;
import com.betfair.cougar.core.api.ServiceVersion;
import com.betfair.cougar.core.api.ev.*;
import com.betfair.cougar.core.api.security.IdentityResolverFactory;
import com.betfair.cougar.core.api.tracing.Tracer;
import com.betfair.cougar.core.impl.CougarInternalOperations;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.betfair.cougar.util.configuration.PropertyConfigurer;
import com.betfair.tornjak.kpi.KPIMonitor;
import com.betfair.tornjak.monitor.MonitorRegistry;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
/**
* Implementation of ExecutionVenue which facilitates service registration.
*
*/
public class ServiceRegisterableExecutionVenue extends BaseExecutionVenue implements ApplicationListener, ServiceRegistrar {
private final static Logger LOGGER = LoggerFactory.getLogger(ServiceRegisterableExecutionVenue.class);
private Map<String, Map<ServiceDefinition, Service>> serviceImplementationMap = new HashMap<String, Map<ServiceDefinition, Service>>();
private Map<ServiceKey, String> serviceStatNames = new HashMap<>();
private Map<ServiceKey, ServiceLogManager> serviceLogManagers = new HashMap<>();
private KPIMonitor stats;
private ServiceLogManagerFactory serviceLogManagerFactory;
private IdentityResolverFactory identityResolverFactory;
protected MonitorRegistry monitorRegistry;
private Tracer tracer;
public void setIdentityResolverFactory(IdentityResolverFactory identityResolverFactory) {
this.identityResolverFactory = identityResolverFactory;
}
public void setServiceLogManagerFactory(ServiceLogManagerFactory serviceLogManagerFactory) {
this.serviceLogManagerFactory = serviceLogManagerFactory;
}
public void setStats(KPIMonitor stats) {
this.stats = stats;
}
public void setMonitorRegistry(MonitorRegistry monitorRegistry) {
this.monitorRegistry = monitorRegistry;
}
public void setTracer(Tracer tracer) {
this.tracer = tracer;
}
public Tracer getTracer() {
return tracer;
}
private void registerServiceDefinition(String namespace, ServiceDefinition serviceDefinition, ExecutableResolver resolver) {
String serviceStatName = getServiceStatName(namespace, serviceDefinition);
for (OperationDefinition op : serviceDefinition.getOperationDefinitions()) {
OperationKey namespacedOperationKey = namespace == null ? op.getOperationKey() : new OperationKey(op.getOperationKey(), namespace);
String timeoutPropertyName = "timeout."+namespacedOperationKey;
String timeoutValue = PropertyConfigurer.getAllLoadedProperties().get(timeoutPropertyName);
if (LOGGER.isInfoEnabled() && timeoutValue != null) {
LOGGER.info("Setting timeout for "+namespacedOperationKey+" to "+timeoutValue+"ms");
}
registerOperation(
namespace,
op,
resolver.resolveExecutable(namespacedOperationKey, this),
stats != null ? new ServiceOperationExecutionTimingRecorder(stats, serviceStatName, op.getOperationKey().getOperationName()) : new NullExecutionTimingRecorder(), timeoutValue != null ? Long.parseLong(timeoutValue) : 0);
}
}
private Map<ServiceDefinition, Service> getImplementationMapForNamespace(String namespace) {
Map<ServiceDefinition, Service> namespacedMap = serviceImplementationMap.get(namespace);
if (namespacedMap == null) {
namespacedMap = new HashMap<ServiceDefinition, Service>();
serviceImplementationMap.put(namespace, namespacedMap);
}
return namespacedMap;
}
@Override
public void registerService(ServiceDefinition serviceDefinition, Service implementation, ExecutableResolver resolver) {
registerService(null, serviceDefinition, implementation, resolver);
}
@Override
public void registerService(String namespace, ServiceDefinition serviceDefinition, Service implementation, ExecutableResolver resolver) {
getImplementationMapForNamespace(namespace).put(serviceDefinition, implementation);
// register the real service executables
registerServiceDefinition(namespace, serviceDefinition, resolver);
if (namespace == null) {
// register the in process one if this is the core service binding
final InProcessExecutable inProcessExecutable = new InProcessExecutable(tracer);
registerServiceDefinition(CougarInternalOperations.COUGAR_IN_PROCESS_NAMESPACE, serviceDefinition, new ExecutableResolver() {
@Override
public Executable resolveExecutable(OperationKey operationKey, ExecutionVenue ev) {
return inProcessExecutable;
}
});
}
LOGGER.info("Initialising {} Service version {}",
serviceDefinition.getServiceName(),
serviceDefinition.getServiceVersion().toString());
implementation.init(getContainerContext(getServiceLogManager(namespace, serviceDefinition)));
LOGGER.info("Initialisation complete");
}
protected Map<String, Map<ServiceDefinition, Service>> getServiceImplementationMap() {
return serviceImplementationMap;
}
/**
* This method returns an unmodifiable map between each namespace and the set of serviceDefinitions bound
* to that namespace. Note that by default the namespace is null, so there could be a null
* namespace with services enumerated within
* @return
*/
public Map<String, Set<ServiceDefinition>> getNamespaceServiceDefinitionMap() {
Map<String, Set<ServiceDefinition>> namespaceServiceDefinitionMap = new HashMap<String, Set<ServiceDefinition>>();
for (String namespace : serviceImplementationMap.keySet()) {
Set<ServiceDefinition> serviceDefinitions = new HashSet<ServiceDefinition>();
namespaceServiceDefinitionMap.put(namespace, serviceDefinitions);
for (ServiceDefinition sd : serviceImplementationMap.get(namespace).keySet()) {
serviceDefinitions.add(sd);
}
}
return Collections.unmodifiableMap(namespaceServiceDefinitionMap);
}
/**
* In the case of ContextRefreshedEvent (that is, once all Spring configuration is loaded), the services will be
* initialised
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
// Firstly dump out all the properties read from the various files
dumpProperties();
// now init the identity resolver
setIdentityResolver(identityResolverFactory.getIdentityResolver());
// start
start();
}
}
private void dumpProperties() {
final Map<String,String> props = PropertyConfigurer.getAllLoadedProperties();
LOGGER.info("Properties loaded from config files and system property overrides");
int longest = 0;
for (Map.Entry<String,String> me : props.entrySet()) {
longest = Math.max(longest, me.getKey().length());
}
for (Map.Entry<String, String> me: props.entrySet()) {
String sysOverride = System.getProperty(me.getKey());
String value = (sysOverride == null ? me.getValue() : sysOverride);
if (me.getKey().toLowerCase().contains("password")) {
value = "*****";
}
LOGGER.info(" {} = {}{}",
StringUtils.rightPad(me.getKey(),longest),
value,
(sysOverride == null ? "" : " [OVERRIDDEN]"));
}
}
private ContainerContext getContainerContext(final ServiceLogManager logManager) {
return new ContainerContext() {
@Override
public ServiceInfo[] getRegisteredServices() {
List<ServiceInfo> services = new ArrayList<ServiceInfo>();
for (Map.Entry<String, Map<ServiceDefinition, Service>> e: getServiceImplementationMap().entrySet()) {
String namespace = e.getKey();
for (Map.Entry<ServiceDefinition, Service> namespacedEntry: e.getValue().entrySet()) {
services.add(makeServiceInfo(namespace, namespacedEntry.getKey(), namespacedEntry.getValue()));
}
}
Collections.sort(services, new Comparator<ServiceInfo>() {
public int compare(ServiceInfo o1, ServiceInfo o2) {
int result = o1.getServiceName().compareTo(o2.getServiceName());
if (result == 0) {
result = o1.getVersion().compareTo(o2.getVersion());
}
return result;
}
});
return services.toArray(new ServiceInfo[services.size()]);
}
@Override
public void registerExtensionLoggerClass(
Class<? extends LogExtension> clazz, int numFieldsLogged) {
logManager.registerExtensionLoggerClass(clazz, numFieldsLogged);
}
@Override
public void registerConnectedObjectExtensionLoggerClass(Class<? extends LogExtension> clazz, int numFieldsLogged) {
logManager.registerConnectedObjectExtensionLoggerClass(clazz, numFieldsLogged);
}
@Override
public MonitorRegistry getMonitorRegistry() {
return monitorRegistry;
}
};
}
private ServiceInfo makeServiceInfo(String namespace, ServiceDefinition serviceDefinition, Service implementation) {
List<String> operations = new ArrayList<>();
for (OperationDefinition operationDefinition : serviceDefinition.getOperationDefinitions()) {
operations.add(operationDefinition.getOperationKey().getOperationName());
}
return new ServiceInfo(namespace,
implementation,
serviceDefinition.getServiceName(),
serviceDefinition.getServiceVersion().toString(),
operations);
}
private ServiceLogManager getServiceLogManager(String namespace, ServiceDefinition serviceDefinition) {
ServiceKey key = new ServiceKey(namespace, serviceDefinition.getServiceName(), serviceDefinition.getServiceVersion());
ServiceLogManager ret = serviceLogManagers.get(key);
if (ret == null) {
ret = serviceLogManagerFactory.create(namespace, serviceDefinition.getServiceName(), serviceDefinition.getServiceVersion());
serviceLogManagers.put(key, ret);
}
return ret;
}
public ServiceLogManager getServiceLogManager(String namespace, String serviceName, ServiceVersion version) {
ServiceKey key = new ServiceKey(namespace, serviceName, version);
ServiceLogManager ret = serviceLogManagers.get(key);
if (ret == null) {
ret = serviceLogManagerFactory.create(namespace, serviceName, version);
serviceLogManagers.put(key, ret);
}
return ret;
}
private String getServiceStatName(String namespace, ServiceDefinition serviceDefinition) {
return getServiceStatName(namespace, serviceDefinition.getServiceName(), serviceDefinition.getServiceVersion());
}
private String getServiceStatName(String namespace, String serviceName, ServiceVersion version) {
ServiceKey key = new ServiceKey(namespace, serviceName, version);
String ret = serviceStatNames.get(key);
if (ret == null) {
ret = serviceName + "." + version + ((namespace == null) ? "" : "." + namespace);
serviceStatNames.put(key, ret);
}
return ret;
}
private static class ServiceKey {
private String namespace;
private String serviceName;
private ServiceVersion version;
private ServiceKey(String namespace, String serviceName, ServiceVersion version) {
this.namespace = namespace;
this.serviceName = serviceName;
this.version = version;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServiceKey serviceKey = (ServiceKey) o;
if (namespace != null ? !namespace.equals(serviceKey.namespace) : serviceKey.namespace != null) return false;
if (serviceName != null ? !serviceName.equals(serviceKey.serviceName) : serviceKey.serviceName != null) return false;
if (version != null ? !version.equals(serviceKey.version) : serviceKey.version != null) return false;
return true;
}
@Override
public int hashCode() {
int result = namespace != null ? namespace.hashCode() : 0;
result = 43 * result + (serviceName != null ? serviceName.hashCode() : 0);
result = 43 * result + (version != null ? version.hashCode() : 0);
return result;
}
}
}