Hierarchical states are not being persisted in the database - spring-statemachine

I am struggling to get the correct state of the hierarchical state machine from the database after persisting it.The parent machine has two states ("IN_ANALYSIS", "OPEN") and the IN_ANALYSIS state has substates("IN_PROGRESS","PENDING_SIGNOFF","PENDING_SIGNOFF2","COMPLETED").
#Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.IN_ANALYSIS)
.state(States.IN_ANALYSIS)
.state(States.OPEN)
.and()
.withStates()
.parent(States.IN_ANALYSIS)
.initial(States.IN_PROGRESS)
.state(States.IN_PROGRESS)
.state(States.PENDING_SIGNOFF)
.state(States.PENDING_SIGNOFF2)
.state(States.COMPLETED,closedEntryAction(), null);
}
Whenever I leave the state machine on one of the intermediate sub-states like(PENDING_SIGNOFF, PENDING_SIGNOFF2) and later fetch it again from the database than the sub-states gets reset to the initial state (IN_PROGRESS).
I am using JPA persistance provided by the spring state machine framework
#Configuration
#Profile("jpa")
public static class JpaPersisterConfig {
#Bean
public StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister(
JpaStateMachineRepository jpaStateMachineRepository) {
return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}
}
and using "DefaultStateMachineService"
#Configuration
public static class ServiceConfig {
#Bean
public StateMachineService<States, Events> stateMachineService(
StateMachineFactory<States, Events> stateMachineFactory,
StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister) {
return new DefaultStateMachineService<States, Events>(stateMachineFactory, stateMachineRuntimePersister);
}
}

It seems Spring saves hierarchical states as a different state machine context as it described in Data Multi Persist example.
Try to configure regions by marking nested states as a region and setting region id. And then you can request state machine by id and region id
StateMachine<States, Events> sm = defaultStateMachineService.acquireStateMachine(stateMachineId + "#" + regionId)

The problem is in the DefaultStateMachineService class, during acquiring SM the service creates a new SM and the JpaPersistingStateMachineInterceptor persists the new SM with an initial state, then the DefaultStateMachineService reads state from DB (the state is already overridden) and calls restoreStateMachine method with the overridden state. So, to fix this issue You have to create your own StateMachineService implementation.
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.Lifecycle;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachineException;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.access.StateMachineAccess;
import org.springframework.statemachine.access.StateMachineFunction;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.service.DefaultStateMachineService;
import org.springframework.statemachine.service.StateMachineService;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
public class PrimeStateMachineService<S, E> implements StateMachineService<S, E>, DisposableBean {
private final static Log log = LogFactory.getLog(DefaultStateMachineService.class);
private final StateMachineFactory<S, E> stateMachineFactory;
private final Map<String, StateMachine<S, E>> machines = new HashMap<String, StateMachine<S, E>>();
private StateMachinePersist<S, E, String> stateMachinePersist;
/**
* Instantiates a new default state machine service.
*
* #param stateMachineFactory the state machine factory
*/
public PrimeStateMachineService(StateMachineFactory<S, E> stateMachineFactory) {
this(stateMachineFactory, null);
}
/**
* Instantiates a new default state machine service.
*
* #param stateMachineFactory the state machine factory
* #param stateMachinePersist the state machine persist
*/
public PrimeStateMachineService(StateMachineFactory<S, E> stateMachineFactory,
StateMachinePersist<S, E, String> stateMachinePersist) {
Assert.notNull(stateMachineFactory, "'stateMachineFactory' must be set");
this.stateMachineFactory = stateMachineFactory;
this.stateMachinePersist = stateMachinePersist;
}
#Override
public final void destroy() throws Exception {
doStop();
}
#Override
public StateMachine<S, E> acquireStateMachine(String machineId) {
return acquireStateMachine(machineId, true);
}
#Override
public StateMachine<S, E> acquireStateMachine(String machineId, boolean start) {
log.info("Acquiring machine with id " + machineId);
StateMachine<S, E> stateMachine;
synchronized (machines) {
stateMachine = machines.get(machineId);
if (isNull(stateMachine)) {
if (nonNull(stateMachinePersist)) {
try {
StateMachineContext<S, E> stateMachineContext = stateMachinePersist.read(machineId);
if (isNull(stateMachineContext)) {
stateMachine = stateMachineFactory.getStateMachine(machineId);
log.info("Getting new machine from factory with id " + machineId);
} else {
stateMachine = restoreStateMachine(stateMachineFactory.getStateMachine(machineId), stateMachineContext);
log.info("State machine restored from repository with id " + machineId);
}
} catch (Exception e) {
log.error("Error handling context", e);
throw new StateMachineException("Unable to read context from store", e);
}
}
machines.put(machineId, stateMachine);
}
}
return handleStart(stateMachine, start);
}
#Override
public void releaseStateMachine(String machineId) {
log.info("Releasing machine with id " + machineId);
synchronized (machines) {
StateMachine<S, E> stateMachine = machines.remove(machineId);
if (stateMachine != null) {
log.info("Found machine with id " + machineId);
stateMachine.stop();
}
}
}
#Override
public void releaseStateMachine(String machineId, boolean stop) {
log.info("Releasing machine with id " + machineId);
synchronized (machines) {
StateMachine<S, E> stateMachine = machines.remove(machineId);
if (stateMachine != null) {
log.info("Found machine with id " + machineId);
handleStop(stateMachine, stop);
}
}
}
/**
* Determines if the given machine identifier denotes a known managed state machine.
*
* #param machineId machine identifier
* #return true if machineId denotes a known managed state machine currently in memory
*/
public boolean hasStateMachine(String machineId) {
synchronized (machines) {
return machines.containsKey(machineId);
}
}
/**
* Sets the state machine persist.
*
* #param stateMachinePersist the state machine persist
*/
public void setStateMachinePersist(StateMachinePersist<S, E, String> stateMachinePersist) {
this.stateMachinePersist = stateMachinePersist;
}
protected void doStop() {
log.info("Entering stop sequence, stopping all managed machines");
synchronized (machines) {
ArrayList<String> machineIds = new ArrayList<>(machines.keySet());
for (String machineId : machineIds) {
releaseStateMachine(machineId, true);
}
}
}
protected StateMachine<S, E> restoreStateMachine(StateMachine<S, E> stateMachine, final StateMachineContext<S, E> stateMachineContext) {
if (stateMachineContext == null) {
return stateMachine;
}
stateMachine.stop();
stateMachine
.getStateMachineAccessor()
.doWithAllRegions(function -> function.resetStateMachine(stateMachineContext));
return stateMachine;
}
protected StateMachine<S, E> handleStart(StateMachine<S, E> stateMachine, boolean start) {
if (start) {
if (!((Lifecycle) stateMachine).isRunning()) {
PrimeStateMachineService.StartListener<S, E> listener = new PrimeStateMachineService.StartListener<>(stateMachine);
stateMachine.addStateListener(listener);
stateMachine.start();
try {
listener.latch.await();
} catch (InterruptedException e) {
}
}
}
return stateMachine;
}
protected StateMachine<S, E> handleStop(StateMachine<S, E> stateMachine, boolean stop) {
if (stop) {
if (((Lifecycle) stateMachine).isRunning()) {
PrimeStateMachineService.StopListener<S, E> listener = new PrimeStateMachineService.StopListener<>(stateMachine);
stateMachine.addStateListener(listener);
stateMachine.stop();
try {
listener.latch.await();
} catch (InterruptedException e) {
}
}
}
return stateMachine;
}
private static class StartListener<S, E> extends StateMachineListenerAdapter<S, E> {
final CountDownLatch latch = new CountDownLatch(1);
final StateMachine<S, E> stateMachine;
public StartListener(StateMachine<S, E> stateMachine) {
this.stateMachine = stateMachine;
}
#Override
public void stateMachineStarted(StateMachine<S, E> stateMachine) {
this.stateMachine.removeStateListener(this);
latch.countDown();
}
}
private static class StopListener<S, E> extends StateMachineListenerAdapter<S, E> {
final CountDownLatch latch = new CountDownLatch(1);
final StateMachine<S, E> stateMachine;
public StopListener(StateMachine<S, E> stateMachine) {
this.stateMachine = stateMachine;
}
#Override
public void stateMachineStopped(StateMachine<S, E> stateMachine) {
this.stateMachine.removeStateListener(this);
latch.countDown();
}
}
}

I had the same problem in my functional testing context. The takeway was to create and seed desired state machines in memory via service (without releasing it) and run the tests end to end. This way, the service did not submit for persistence, which was causing the state rollback to the initial state
public static StateMachine restoreMachine(final StateMachine<States, Events> stateMachine,
final States currentState, final ExtendedState extendedState) {
stateMachine.getStateMachineAccessor().doWithAllRegions(t ->
t.resetStateMachine(
new DefaultStateMachineContext<>(currentState, Events.ANY_DESIRED_STATE, null, extendedState, null, "your-machine-id")));
return stateMachine;
}

Related

How can i use #Retryable(label="...") for logging or monitoring purposes?

I have simple retryable method annotated with
#Retryable(label = "myLabel")
According to documentation, it should be
A unique label for statistics reporting
Is this label accessible inside RetryListener? How can i use it?
The label is available in the RetryContext.NAME attribute in the context.
#Component
class Foo {
private static final Logger log = LoggerFactory.getLogger(Foo.class);
#Retryable(label = "myLabel")
public void retriable() {
log.info("Here with label: " + RetrySynchronizationManager.getContext().getAttribute(RetryContext.NAME));
throw new RuntimeException("test");
}
#Recover
public void recover(Exception e) {
log.info("Recovered");
}
}
The context is available in the listener methods.
There's a new feature in 1.3.
1.3 is not released yet but there is a snapshot 1.3.0.BUILD-SNAPSHOT in the spring snapshots repo https://repo.spring.io/snapshot.
This also gives you access to the method invocation.
#Component
class MyRetryListener extends MethodInvocationRetryListenerSupport {
private static final Logger log = LoggerFactory.getLogger(MyRetryListener.class);
#Override
protected <T, E extends Throwable> boolean doOpen(RetryContext context,
MethodInvocationRetryCallback<T, E> callback) {
log.info("Invocation of method: " + callback.getInvocation().getMethod().toGenericString()
+ " with label: " + callback.getLabel());
return super.doOpen(context, callback);
}
}
#Component
class Foo {
private static final Logger log = LoggerFactory.getLogger(Foo.class);
#Retryable(label = "myLabel")
public void retriable() {
log.info("Here");
throw new RuntimeException("test");
}
#Recover
public void recover(Exception e) {
log.info("Recovered");
}
}

Spring equivalent for CDI Instance

I am new to Spring and i need to convert a CDI class to Spring.
I have the below code in CDI
#Inject
private Instance<HealthCheck> healthChecks;
I then iterate over healthChecks.
I found a similar question What is the Spring equivalent for CDI's Instance, or Guices Provider , where it was advised to use
#Inject
Provider<MyObject> myObjectInstance;
//...
MyObject myObjectInstance.get();
However, since provider does not implements iterable, I am not able to iterate.
Can someone please help me on how can I convert the CDI code block to spring.
create a new file spring.factories inside META-INF with following content:
org.springframework.context.ApplicationContextInitializer=package_name.CustomApplicationContextInitializer
or you can use it in your junit test like:
#SpringApplicationConfiguration(initializers = CustomApplicationContextInitializer.class)
And now you can use like:
#Autowired
private Instance<HealthCheck> healthChecks;
CustomApplicationContextInitializer.class
public class CustomApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// Configure custom CustomAutowireCandidateResolver to handle CDI
// Instance<T> dependency requests
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
beanFactory.setAutowireCandidateResolver(new CustomAutowireCandidateResolver());
}
}
CustomAutowireCandidateResolver.class
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Priority;
import javax.enterprise.inject.Instance;
import javax.enterprise.util.TypeLiteral;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.core.annotation.Order;
import org.springframework.util.ClassUtils;
public class CustomAutowireCandidateResolver extends ContextAnnotationAutowireCandidateResolver {
static final boolean IS_CDI_INSTANCE_CLASS_PRESENT = ClassUtils.isPresent("javax.enterprise.inject.Instance", null);
#Override
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName) {
if (IS_CDI_INSTANCE_CLASS_PRESENT && Instance.class.equals(descriptor.getDependencyType())) {
// TODO refactor getLazyResolutionProxyIfNecessary to allow to
// customize lazy dependency resolution for Instance<T>
return getInstanceAdapterFor(descriptor);
}
return super.getLazyResolutionProxyIfNecessary(descriptor, beanName);
}
#SuppressWarnings({ "unchecked", "rawtypes" })
private Object getInstanceAdapterFor(DependencyDescriptor descriptor) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) getBeanFactory();
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) listableBeanFactory;
// Instance<TargetType>
Class targetType = descriptor.getResolvableType().getGeneric(0).getRawClass();
Map<String, Object> beansOfType = listableBeanFactory.getBeansOfType(targetType);
List<Bean> beansInstances = beansOfType.entrySet().stream() //
.map(e -> new Bean(e.getValue(), registry.getBeanDefinition(e.getKey()).isPrimary()))//
.collect(Collectors.toList());
Annotation[] qualifiers = retainQualifierAnnotations(descriptor.getAnnotations());
Beans beans = new Beans(targetType, beansInstances);
return qualifiers.length == 0 ? beans : beans.select(qualifiers);
}
private Annotation[] retainQualifierAnnotations(Annotation[] annotations) {
return Arrays.stream(annotations) //
.filter(a -> a.annotationType().isAnnotationPresent(Qualifier.class)) //
.toArray(Annotation[]::new);
}
static class Beans<T> implements Instance<T> {
private final List<Bean> beans;
private final Class<?> type;
public Beans(Class<?> type, List<Bean> beans) {
this.type = type;
this.beans = beans;
}
protected List<Bean> getBeans() {
return beans;
}
#Override
public T get() {
return (T) findDefaultInstance();
}
protected Object findDefaultInstance() {
List<Bean> beans = getBeans();
if (beans.size() == 1) {
return beans.get(0).getInstance();
}
Object highestPrioBean = returnPrimaryOrHighestPriorityBean(beans);
if (highestPrioBean != null) {
return highestPrioBean;
}
// TODO figure out a sane default to use here - maybe throw an
// exception?
return beans.get(0).getInstance();
}
private Object returnPrimaryOrHighestPriorityBean(List<Bean> beans) {
long highestPriority = Integer.MIN_VALUE;
Object highestPrioBean = null;
for (Bean bean : beans) {
if (bean.isPrimary()) {
return bean.getInstance();
}
// TODO figure out to retrieve order from BeanDefinition /
// BeanDeclaration
Object instance = bean.getInstance();
Order order = instance.getClass().getAnnotation(Order.class);
if (order != null) {
if (order.value() > highestPriority) {
highestPriority = order.value();
highestPrioBean = instance;
}
}
Priority priority = instance.getClass().getAnnotation(Priority.class);
if (priority != null) {
if (priority.value() > highestPriority) {
highestPriority = priority.value();
highestPrioBean = instance;
}
}
}
return highestPrioBean;
}
#Override
#SuppressWarnings("unchecked")
public Instance<T> select(Annotation... qualifiers) {
return select((Class<T>) type, qualifiers);
}
#Override
public <U extends T> Instance<U> select(Class<U> subtype, Annotation... qualifiers) {
return new Beans<U>(subtype, filterBeans(subtype, qualifiers));
}
protected List<Bean> filterBeans(Class<?> subtype, Annotation... qualifiers) {
List<Annotation> requiredQualifiers = Arrays.asList(qualifiers);
return getBeans().stream() //
.filter(bean -> subtype.isInstance(bean.getInstance())) //
.filter(bean -> bean.getAnnotations().containsAll(requiredQualifiers)) //
.collect(Collectors.toList());
}
#Override
public <U extends T> Instance<U> select(TypeLiteral<U> subtype, Annotation... qualifiers) {
// TODO implement (Class<U> subtype, Annotation... qualifiers) via
// select(TypeLiteral<U> subtype, Annotation... qualifiers)
return select(subtype.getRawType(), qualifiers);
}
#Override
public Iterator<T> iterator() {
return getBeans().stream().map(bean -> (T) bean.getInstance()).iterator();
}
#Override
public boolean isUnsatisfied() {
return getBeans().isEmpty();
}
#Override
public boolean isAmbiguous() {
return getBeans().size() > 1;
}
#Override
public void destroy(Object bean) {
if (bean instanceof DisposableBean) {
try {
((DisposableBean) bean).destroy();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
static class Bean {
private final boolean primary;
private final Object instance;
private final List<Annotation> annotations;
public Bean(Object instance, boolean primary) {
this.primary = primary;
this.instance = instance;
this.annotations = Arrays.asList(instance.getClass().getAnnotations());
}
public Object getInstance() {
return instance;
}
public boolean isPrimary() {
return primary;
}
public List<Annotation> getAnnotations() {
return annotations;
}
}
}
here is full source link: https://github.com/thomasdarimont/spring-boot-cdi-instance-example

Spring Websocket + Stomp + SockJs

I built a chat application using the portfolio websocket sample as a guide. I am using spring boot 1.3.3, ActiveMQ, STOMP and the UI is built with KnockoutJs and running on Windows 2012 server.
My issue is after appx 1000 connections to the chat server (spring boot) the server stop accepting any more connections.
I played with different heartbeat settings and also messages size settings etc to no avail.
Did anyone build such a chat / websocket application and is able to achieve more than 1000 concurrent connections?
I spent over a week researching, tweaking the code and I also changed the Windows 2012 server connection limit ( seem like 2012 removed TCP connection limit).
Any help or pointers will be greatly appreciated.
/**
*
*/
package com.test.chat;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.config.StompBrokerRelayRegistration;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.SockJsServiceRegistration;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
import com.test.chat.application.event.StompConnectEvent;
import com.test.chat.application.event.StompConnectedEvent;
import com.test.chat.application.event.StompDisconnectEvent;
/**
* #author pgobin
*
* https://www.youtube.com/watch?v=mmIza3L64Ic
*
*/
#Configuration
#EnableWebSocketMessageBroker
#ComponentScan("com.test.chat")
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
private static final Logger log = Logger.getLogger(WebSocketConfig.class);
#Value("${StompBrokerRelay.host}")
String StompBrokerRelayHost;
#Value("${StompBrokerRelay.port}")
int StompBrokerRelayPort;
#Value("${MessageBroker.User}")
String brokerUser;
#Value("${MessageBroker.Password}")
String brokerPassword;
#Value("${MessageBrokerStompClient.User}")
String stompClientUser;
#Value("${MessageBrokerStompClient.Password}")
String stompClientPassword;
#Value("${sockjs.setHttpMessageCacheSize}")
int sockjs_setHttpMessageCacheSize;
#Value("${sockjs.setStreamBytesLimit}")
int sockjs_setStreamBytesLimit;
#Value("${sockjs.setDisconnectDelay:20000}")
int sockjs_setDisconnectDelay;
#Value("${sockjs.setHeartbeatTime:30000}")
int sockjs_setHeartbeatTime;
// WebSocketTransport settings
#Value("${WebSocketTransportRegistration.MessageSizeLimit:131072}")
int MessageSizeLimit;
#Value("${WebSocketTransportRegistration.SendTimeLimit:15000}")
int SendTimeLimit;
#Value("${WebSocketTransportRegistration.SendBufferSizeLimit:524288}")
int SendBufferSizeLimit;
// ClientOutboundChannel configs
#Value("${ClientOutboundChannel.corePoolSize:25}")
int ClientOutboundChannelcorePoolSize;
#Value("${ClientOutboundChannel.maxPoolSize:50}")
int ClientOutboundChannelmaxPoolSize;
// ClientInboundChannel configs
#Value("${ClientInboundChannel.corePoolSize:25}")
int ClientInboundChannelcorePoolSize;
#Value("${ClientInboundChannel.maxPoolSize:50}")
int ClientInboundChannelmaxPoolSize;
/****
*
*/
#Override
public void configureMessageBroker(MessageBrokerRegistry messageBrokerRegistry)
{
// Destination Prefix - Connect to default in-memory broker
// messageBrokerRegistry.enableSimpleBroker("/topic/", "/queue/");
// connect to AMQ
StompBrokerRelayRegistration broker = messageBrokerRegistry.enableStompBrokerRelay("/queue/", "/topic/");
broker.setRelayHost(StompBrokerRelayHost);
broker.setRelayPort(StompBrokerRelayPort);
broker.setSystemLogin(brokerUser);
broker.setSystemPasscode(brokerPassword);
broker.setClientLogin(stompClientUser);
broker.setClientPasscode(stompClientPassword);
// broker.setVirtualHost(virtualHost)
messageBrokerRegistry.setApplicationDestinationPrefixes("/app");
}
/*****
* https://github.com/rstoyanchev/spring-websocket-test/issues/4
*/
#Override
public void registerStompEndpoints(StompEndpointRegistry stompRegistry)
{
String wsOrigins = AppConfig.getEnv().getProperty("websocket.security.allow.origins", "http://localhost:8080");
log.info("#### ALLOWING MESSAGING ONLY FROM ORIGINS:" + wsOrigins + ". ALL OTHERS WILL BE BLOCKED ####");
String[] cors = StringUtils.split(AppConfig.getEnv().getProperty("websocket.security.allow.origins", "http://localhost:8080"), ",");
// WebSocket URL prefix
SockJsServiceRegistration reg = stompRegistry.addEndpoint("/chat").setAllowedOrigins(cors).withSockJS()
.setStreamBytesLimit(sockjs_setStreamBytesLimit).setDisconnectDelay(sockjs_setDisconnectDelay)
.setHttpMessageCacheSize(sockjs_setHttpMessageCacheSize).setHeartbeatTime(sockjs_setHeartbeatTime).setWebSocketEnabled(true)
.setSupressCors(false);
}
#Bean
public ServletServerContainerFactoryBean createWebSocketContainer()
{
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
container.setAsyncSendTimeout(5000);
container.setMaxSessionIdleTimeout(600000);
return container;
}
#Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration)
{
registration.setMessageSizeLimit(MessageSizeLimit);
registration.setSendTimeLimit(SendTimeLimit);
registration.setSendBufferSizeLimit(SendBufferSizeLimit);
}
/**
* Configure the {#link org.springframework.messaging.MessageChannel} used
* for outgoing messages to WebSocket clients. By default the channel is
* backed by a thread pool of size 1. It is recommended to customize thread
* pool settings for production use.
*/
#Override
public void configureClientOutboundChannel(ChannelRegistration registration)
{
registration.taskExecutor().corePoolSize(ClientOutboundChannelcorePoolSize).maxPoolSize(ClientOutboundChannelmaxPoolSize);
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration)
{
registration.taskExecutor().corePoolSize(ClientInboundChannelcorePoolSize).maxPoolSize(ClientInboundChannelmaxPoolSize);
}
/***
* Intercepts a connect event
*
* #return
*/
#Bean
public StompConnectEvent presenceChannelInterceptorOnConnect(SimpMessagingTemplate messagingTemplate)
{
return new StompConnectEvent(messagingTemplate);
}
/*
* #Bean public StompConnectedEvent
* presenceChannelInterceptorOnConnected(SimpMessagingTemplate
* messagingTemplate) { return new StompConnectedEvent(messagingTemplate); }
*/
#Bean
public StompDisconnectEvent presenceChannelInterceptorOnDisconnect(SimpMessagingTemplate messagingTemplate)
{
return new StompDisconnectEvent(messagingTemplate);
}
}
And my client test code:
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.simp.stomp.ConnectionLostException;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompFrameHandler;
import org.springframework.messaging.simp.stomp.StompHeaders;
import org.springframework.messaging.simp.stomp.StompSession;
import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.socket.WebSocketHttpHeaders;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.messaging.WebSocketStompClient;
import org.springframework.web.socket.sockjs.client.SockJsClient;
import org.springframework.web.socket.sockjs.client.Transport;
import org.springframework.web.socket.sockjs.client.WebSocketTransport;
public static void runTest(final long userUid, final int clientNum)
{
//String stompUrl = "ws://localhost:8080/chat";
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.afterPropertiesSet();
StandardWebSocketClient webSocketClient = new StandardWebSocketClient();
List<Transport> transports = new ArrayList<>();
transports.add(new WebSocketTransport(webSocketClient));
SockJsClient sockJsClient = new SockJsClient(transports);
WebSocketStompClient stompClient = new WebSocketStompClient(sockJsClient);
// stompClient.setMessageConverter(new StringMessageConverter());
stompClient.setMessageConverter(new org.springframework.messaging.converter.MappingJackson2MessageConverter());
stompClient.setTaskScheduler(taskScheduler);
stompClient.setDefaultHeartbeat(new long[] { 0, 0 });
ConsumerStompSessionHandler handler = new ConsumerStompSessionHandler(BROADCAST_MESSAGE_COUNT, connectLatch, subscribeLatch, messageLatch,
disconnectLatch, failure, clientNum);
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("userid", userUid);
WebSocketHttpHeaders wsHeaders = new WebSocketHttpHeaders();
wsHeaders.add("userid", "" + userUid);
StompHeaders stompHeaders = new StompHeaders();
stompHeaders.add("userid", "" + userUid);
stompHeaders.add("channelID", "java-" + System.currentTimeMillis());
stompHeaders.add("platform", "Windows");
stompHeaders.add("clientIP", "10.1.1.1");
// stompClient.connect(stompUrl, handler, params);
stompClient.connect(stompUrl, wsHeaders, stompHeaders, handler, params);
}
private static class ConsumerStompSessionHandler extends StompSessionHandlerAdapter {
private final int expectedMessageCount;
private final CountDownLatch connectLatch;
private final CountDownLatch subscribeLatch;
private final CountDownLatch messageLatch;
private final CountDownLatch disconnectLatch;
private final AtomicReference<Throwable> failure;
private AtomicInteger messageCount = new AtomicInteger(0);
int clientNum = 0;
public ConsumerStompSessionHandler(int expectedMessageCount, CountDownLatch connectLatch, CountDownLatch subscribeLatch,
CountDownLatch messageLatch, CountDownLatch disconnectLatch, AtomicReference<Throwable> failure, int clientNum)
{
this.expectedMessageCount = expectedMessageCount;
this.connectLatch = connectLatch;
this.subscribeLatch = subscribeLatch;
this.messageLatch = messageLatch;
this.disconnectLatch = disconnectLatch;
this.failure = failure;
this.clientNum = clientNum;
}
#Override
public void afterConnected(final StompSession session, StompHeaders connectedHeaders)
{
__ActiveConn = __ActiveConn + 1;
this.connectLatch.countDown();
session.setAutoReceipt(true);
final RequestUserList req = new RequestUserList();
req.setCustomerid(customerID);
String channelID = System.currentTimeMillis() + "";
String subscribeChannel = __SUBSCRIBE_PREDICATE_QUEUE + channelID;
final String sendChannel = __SEND_PREDICATE + "userListOnline";
req.setChannelID(channelID);
// session.send(sendChannel, req);
// System.out.println("Client " + clientNum + " connected");
session.subscribe(subscribeChannel, new StompFrameHandler() {
#Override
public Type getPayloadType(StompHeaders headers)
{
System.out.println("Got ResponseH");
return String.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload)
{
System.out.println("Got ResponseA");
/* if (messageCount.incrementAndGet() == expectedMessageCount)
{
messageLatch.countDown();
disconnectLatch.countDown();
session.disconnect();
}*/
}
}).addReceiptTask(new Runnable() {
#Override
public void run()
{
System.out.println("Got Response for client " + clientNum);
//subscribeLatch.countDown();
}
});
// session.send(sendChannel, req);
}
#Override
public void handleTransportError(StompSession session, Throwable exception)
{
__ErrorConn = __ErrorConn + 1;
logger.error("Transport error", exception);
this.failure.set(exception);
if (exception instanceof ConnectionLostException)
{
this.disconnectLatch.countDown();
}
}
#Override
public void handleException(StompSession s, StompCommand c, StompHeaders h, byte[] p, Throwable ex)
{
logger.error("Handling exception", ex);
this.failure.set(ex);
}
#Override
public void handleFrame(StompHeaders headers, Object payload)
{
System.out.println("Got ResponseF");
Exception ex = new Exception(headers.toString());
logger.error("STOMP ERROR frame", ex);
this.failure.set(ex);
}
#Override
public String toString()
{
return "ConsumerStompSessionHandler[messageCount=" + this.messageCount + "]";
}
}
public static void main(String[] args)
{
try
{
int clientCount = 3000;
for (int x = 0; x < clientCount; x++){
runTest(121807, x+1);
}
System.out.println("DONE...");
System.out.println("Live Connections = " + __ActiveConn);
System.out.println("Error Connections = " + __ErrorConn);
/*
for (int x = 0; x < clientCount; x++)
{
final int clientNum = x;
ThreadPoolManager.executorService.execute(new Runnable() {
#Override
public void run()
{
try
{
Thread.sleep(500);
} catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
runTest(121807, clientNum);
}
});
}
*/
System.out.println("DONE..Waiting..");
Thread.sleep(1000000);
} catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}

Jetty Websocket Compilation Errors

I am trying to do an Jetty Web Socket example .
I copied a example from internet , which was working fine when i deployed directly into server without making any chnages .
But when i copied the Source (the servlet) into Eclipse IDE , it was giving Compilation
Exceptions related to
The method onClose(int, String) of type Html5Servlet.StockTickerSocket must override a superclass method
- The method onOpen(WebSocket.Connection) of type Html5Servlet.StockTickerSocket must override a superclass method
The method onMessage(String) of type Html5Servlet.StockTickerSocket must override a superclass method
This is my servlet , i kept the jars as it is mentioned in that example
package org.ajeesh.app;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;
public class Html5Servlet extends WebSocketServlet {
private AtomicInteger index = new AtomicInteger();
private static final List<String> tickers = new ArrayList<String>();
static{
tickers.add("ajeesh");
tickers.add("peeyu");
tickers.add("kidillan");
tickers.add("entammo");
}
/**
*
*/
private static final long serialVersionUID = 1L;
public WebSocket doWebSocketConnect(HttpServletRequest req, String resp) {
System.out.println("On server");
return new StockTickerSocket();
}
protected String getMyJsonTicker(){
StringBuilder start=new StringBuilder("{");
start.append("\"stocks\":[");
int counter=0;
for (String aTicker : tickers) {
counter++;
start.append("{ \"ticker\":\""+aTicker +"\""+","+"\"price\":\""+index.incrementAndGet()+"\" }");
if(counter<tickers.size()){
start.append(",");
}
}
start.append("]");
start.append("}");
return start.toString();
}
public class StockTickerSocket implements WebSocket.OnTextMessage{
private Connection connection;
private Timer timer;
#Override
public void onClose(int arg0, String arg1) {
System.out.println("Web socket closed!");
}
#Override
public void onOpen(Connection connection) {
System.out.println("onOpen!");
this.connection=connection;
this.timer=new Timer();
}
#Override
public void onMessage(String data) {
System.out.println("onMessage!");
if(data.indexOf("disconnect")>=0){
connection.close();
timer.cancel();
}else{
sendMessage();
}
}
private void sendMessage() {
System.out.println("sendMessage!");
if(connection==null||!connection.isOpen()){
System.out.println("Connection is closed!!");
return;
}
timer.schedule(new TimerTask() {
#Override
public void run() {
try{
System.out.println("Running task");
connection.sendMessage(getMyJsonTicker());
}
catch (IOException e) {
e.printStackTrace();
}
}
}, new Date(),5000);
}
}
}

Spring/JSF2 and #ViewScoped

I want to use spring managed beans for my JSF2 controllers so that autowiring works. I know that there is no #ViewScoped in spring and I know a few implementations of #ViewScoped floating around various blogs (one from the primefaces lead).
Is any of them used in a real application and considered stable? Maybe one of them is recommended or widely used and I'm just not able to find it.
There is one :) Without memory leaks and with #PreDestroy support. Tested in production. Here.
package org.nkey.primefaces.scopes.test.spring.scope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.PreDestroyViewMapEvent;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
/**
* #author m.nikolaev Date: 21.11.12 Time: 0:37
*/
public class ViewScope implements Scope, Serializable, HttpSessionBindingListener {
private static final Logger LOGGER = LoggerFactory.getLogger(ViewScope.class);
private final WeakHashMap<HttpSession, Set<ViewScopeViewMapListener>> sessionToListeners = new WeakHashMap<>();
#Override
public Object get(String name, ObjectFactory objectFactory) {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (viewMap) {
if (viewMap.containsKey(name)) {
return viewMap.get(name);
} else {
LOGGER.debug("Creating bean {}", name);
Object object = objectFactory.getObject();
viewMap.put(name, object);
return object;
}
}
}
#Override
public Object remove(String name) {
throw new UnsupportedOperationException();
}
#Override
public String getConversationId() {
return null;
}
#Override
public void registerDestructionCallback(String name, Runnable callback) {
LOGGER.debug("registerDestructionCallback for bean {}", name);
UIViewRoot viewRoot = FacesContext.getCurrentInstance().getViewRoot();
ViewScopeViewMapListener listener =
new ViewScopeViewMapListener(viewRoot, name, callback, this);
viewRoot.subscribeToViewEvent(PreDestroyViewMapEvent.class, listener);
HttpSession httpSession = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(true);
final Set<ViewScopeViewMapListener> sessionListeners;
synchronized (sessionToListeners) {
if (!sessionToListeners.containsKey(httpSession)) {
sessionToListeners.put(httpSession, new HashSet<ViewScopeViewMapListener>());
}
sessionListeners = sessionToListeners.get(httpSession);
}
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (sessionListeners) {
Set<ViewScopeViewMapListener> toRemove = new HashSet<>();
for (ViewScopeViewMapListener viewMapListener : sessionListeners) {
if (viewMapListener.checkRoot()) {
toRemove.add(viewMapListener);
}
}
sessionListeners.removeAll(toRemove);
sessionListeners.add(listener);
}
if (!FacesContext.getCurrentInstance().getExternalContext().getSessionMap().containsKey("sessionBindingListener")) {
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("sessionBindingListener", this);
}
}
#Override
public Object resolveContextualObject(String key) {
return null;
}
#Override
public void valueBound(HttpSessionBindingEvent event) {
LOGGER.debug("Session event bound {}", event.getName());
}
#Override
public void valueUnbound(HttpSessionBindingEvent event) {
LOGGER.debug("Session event unbound {}", event.getName());
final Set<ViewScopeViewMapListener> listeners;
synchronized (sessionToListeners) {
if (sessionToListeners.containsKey(event.getSession())) {
listeners = sessionToListeners.get(event.getSession());
sessionToListeners.remove(event.getSession());
} else {
listeners = null;
}
}
if (listeners != null) {
for (ViewScopeViewMapListener listener : listeners) {
listener.doCallback();
}
}
}
public void clearFromListener(ViewScopeViewMapListener listener) {
LOGGER.debug("Removing listener from map");
HttpSession httpSession = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(false);
if (httpSession != null) {
synchronized (sessionToListeners) {
if (sessionToListeners.containsKey(httpSession)) {
sessionToListeners.get(httpSession).remove(listener);
}
}
}
}
}

Resources