فهرست منبع

порефакторил стейты, починил сохранение и загрузку стейтов, теперь стейты грузятся нормально, добавил первые сообщения приветствия через шаблонизатор

kpmy 8 سال پیش
والد
کامیت
af86985e2c
25فایلهای تغییر یافته به همراه476 افزوده شده و 197 حذف شده
  1. 6 0
      pom.xml
  2. 21 0
      src/main/java/in/ocsf/these/days/app/Ctx.java
  3. 2 4
      src/main/java/in/ocsf/these/days/app/messaging/ChatHelper.java
  4. 2 3
      src/main/java/in/ocsf/these/days/app/repo/ChatRepositoryCustom.java
  5. 4 4
      src/main/java/in/ocsf/these/days/app/repo/ChatRepositoryImpl.java
  6. 33 0
      src/main/java/in/ocsf/these/days/app/service/CommandService.java
  7. 41 50
      src/main/java/in/ocsf/these/days/app/service/UpdateService.java
  8. 18 7
      src/main/java/in/ocsf/these/days/app/state/ChatType.java
  9. 20 0
      src/main/java/in/ocsf/these/days/app/state/IChat.java
  10. 17 0
      src/main/java/in/ocsf/these/days/app/state/IState.java
  11. 1 1
      src/main/java/in/ocsf/these/days/app/state/InChatStateMachinePersist.java
  12. 46 20
      src/main/java/in/ocsf/these/days/app/state/StateService.java
  13. 0 5
      src/main/java/in/ocsf/these/days/app/state/UserEvent.java
  14. 0 5
      src/main/java/in/ocsf/these/days/app/state/UserState.java
  15. 0 52
      src/main/java/in/ocsf/these/days/app/state/UserStateMachineFactoryConfig.java
  16. 0 12
      src/main/java/in/ocsf/these/days/app/state/WelcomeChat.java
  17. 0 5
      src/main/java/in/ocsf/these/days/app/state/WelcomeChatEvent.java
  18. 0 5
      src/main/java/in/ocsf/these/days/app/state/WelcomeChatState.java
  19. 55 0
      src/main/java/in/ocsf/these/days/app/state/account/AccountStateMachineFactoryConfig.java
  20. 83 0
      src/main/java/in/ocsf/these/days/app/state/account/Accounted.java
  21. 58 0
      src/main/java/in/ocsf/these/days/app/state/chat/WelcomeChat.java
  22. 9 14
      src/main/java/in/ocsf/these/days/app/state/chat/WelcomeChatStateMachineFactoryConfig.java
  23. 9 10
      src/main/java/in/ocsf/these/days/app/state/utils/BeanMapPersist.java
  24. 41 0
      src/main/java/in/ocsf/these/days/app/state/utils/MessageTemplate.java
  25. 10 0
      src/main/java/in/ocsf/these/days/app/state/utils/PersistentStateMachineContext.java

+ 6 - 0
pom.xml

@@ -91,6 +91,12 @@
             <version>1.9.3</version>
         </dependency>
 
+        <dependency>
+            <groupId>com.github.spullara.mustache.java</groupId>
+            <artifactId>compiler</artifactId>
+            <version>0.9.2</version>
+        </dependency>
+
     </dependencies>
 
     <build>

+ 21 - 0
src/main/java/in/ocsf/these/days/app/Ctx.java

@@ -0,0 +1,21 @@
+package in.ocsf.these.days.app;/* kpmy 25.02.2017 */
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Service;
+
+@Service
+public class Ctx implements ApplicationContextAware {
+
+    private static ApplicationContext context;
+
+    public static ApplicationContext getContext() {
+        return context;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        context = applicationContext;
+    }
+}

+ 2 - 4
src/main/java/in/ocsf/these/days/app/messaging/ChatHelper.java

@@ -3,9 +3,7 @@ package in.ocsf.these.days.app.messaging;/* kpmy 20.02.2017 */
 import com.pengrad.telegrambot.Callback;
 import com.pengrad.telegrambot.TelegramBot;
 import com.pengrad.telegrambot.TelegramBotAdapter;
-import com.pengrad.telegrambot.model.request.Keyboard;
 import com.pengrad.telegrambot.model.request.ParseMode;
-import com.pengrad.telegrambot.model.request.ReplyKeyboardMarkup;
 import com.pengrad.telegrambot.request.SendMessage;
 import com.pengrad.telegrambot.response.SendResponse;
 import org.apache.log4j.Logger;
@@ -28,8 +26,8 @@ public class ChatHelper {
 
     @Async
     public void sendSimpleTextMessage(Long chatId, String text) {
-        Keyboard keyboard = new ReplyKeyboardMarkup(new String[][]{{"пырвет"}});
-        sendMessage(new SendMessage(chatId, text).parseMode(ParseMode.Markdown).replyMarkup(keyboard));
+        //Keyboard keyboard = new ReplyKeyboardMarkup(new String[][]{{"пырвет"}});
+        sendMessage(new SendMessage(chatId, text).parseMode(ParseMode.Markdown));
     }
 
     public Optional<SendResponse> sendMessage(SendMessage request) {

+ 2 - 3
src/main/java/in/ocsf/these/days/app/repo/ChatRepositoryCustom.java

@@ -1,12 +1,11 @@
 package in.ocsf.these.days.app.repo;/* kpmy 21.02.2017 */
 
 import in.ocsf.these.days.app.object.Chat;
-import in.ocsf.these.days.app.object.ChatState;
 
-import java.util.List;
+import java.util.Optional;
 
 public interface ChatRepositoryCustom {
-    List<Chat> findByUserIdAndStateThenActualize(Long id, ChatState state);
+    Optional<Chat> findByUserIdAndStateThenActualize(Long id);
 
     void findByUserIdAndThenClose(Long id);
 }

+ 4 - 4
src/main/java/in/ocsf/these/days/app/repo/ChatRepositoryImpl.java

@@ -8,7 +8,7 @@ import org.springframework.context.annotation.Lazy;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.List;
-import java.util.stream.Collectors;
+import java.util.Optional;
 
 public class ChatRepositoryImpl implements ChatRepositoryCustom {
 
@@ -17,8 +17,8 @@ public class ChatRepositoryImpl implements ChatRepositoryCustom {
     private ChatRepository chatRepo;
 
     @Override
-    public List<Chat> findByUserIdAndStateThenActualize(Long id, ChatState state) {
-        List<Chat> cl = chatRepo.findByUserIdAndState(id, state);
+    public Optional<Chat> findByUserIdAndStateThenActualize(Long id) {
+        List<Chat> cl = chatRepo.findByUserIdAndState(id, ChatState.open);
         LocalDateTime now = LocalDateTime.now();
         for (Chat c : cl) {
             LocalDateTime cd = LocalDateTime.ofInstant(c.getStarted().toInstant(), ZoneId.systemDefault());
@@ -27,7 +27,7 @@ public class ChatRepositoryImpl implements ChatRepositoryCustom {
                 chatRepo.save(c);
             }
         }
-        return cl.stream().filter(c -> c.getState().equals(ChatState.open)).collect(Collectors.toList());
+        return cl.stream().filter(c -> c.getState().equals(ChatState.open)).findAny();
     }
 
     @Override

+ 33 - 0
src/main/java/in/ocsf/these/days/app/service/CommandService.java

@@ -0,0 +1,33 @@
+package in.ocsf.these.days.app.service;/* kpmy 25.02.2017 */
+
+import in.ocsf.these.days.app.messaging.UpdateHelper;
+import in.ocsf.these.days.app.state.IChat;
+import in.ocsf.these.days.app.state.account.Accounted;
+import org.apache.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.statemachine.StateMachine;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class CommandService implements IChat {
+
+    private final Logger log = Logger.getLogger(getClass());
+
+    @Autowired
+    private Accounted accounted;
+
+    @Override
+    public void handle(StateMachine state) {
+        UpdateHelper upd = (UpdateHelper) get(state, MESSAGE);
+        List<String> cmd = upd.getSplitCommand();
+        switch (cmd.get(0)) {
+            case "/start":
+                accounted.handleCommand(cmd, state);
+                break;
+            default:
+                throw new RuntimeException(cmd.get(0));
+        }
+    }
+}

+ 41 - 50
src/main/java/in/ocsf/these/days/app/service/UpdateService.java

@@ -1,21 +1,20 @@
 package in.ocsf.these.days.app.service;/* kpmy 19.02.2017 */
 
 import com.pengrad.telegrambot.model.Update;
-import in.ocsf.these.days.app.messaging.ChatHelper;
 import in.ocsf.these.days.app.messaging.UpdateHelper;
 import in.ocsf.these.days.app.object.Chat;
 import in.ocsf.these.days.app.object.User;
-import in.ocsf.these.days.app.repo.CardRepositrory;
 import in.ocsf.these.days.app.repo.ChatRepository;
-import in.ocsf.these.days.app.repo.MessageRepository;
 import in.ocsf.these.days.app.repo.UserRepository;
-import in.ocsf.these.days.app.state.*;
+import in.ocsf.these.days.app.state.IChat;
+import in.ocsf.these.days.app.state.StateService;
+import in.ocsf.these.days.app.state.account.Accounted;
 import org.apache.log4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
 import org.springframework.statemachine.StateMachine;
 import org.springframework.stereotype.Service;
 
-import java.util.List;
 import java.util.Optional;
 
 @Service
@@ -24,13 +23,10 @@ public class UpdateService {
     private final Logger log = Logger.getLogger(getClass());
 
     @Autowired
-    private UserRepository userRepo;
-
-    @Autowired
-    private MessageRepository msgRepo;
+    private ApplicationContext context;
 
     @Autowired
-    private CardRepositrory cardRepo;
+    private UserRepository userRepo;
 
     @Autowired
     private StateService stateService;
@@ -39,51 +35,46 @@ public class UpdateService {
     private ChatRepository chatRepo;
 
     @Autowired
-    private ChatHelper chat;
-
-    private void handleUpdate(UpdateHelper upd) throws Exception {
-        if (!upd.isEdit()) {
-            if (upd.hasText()) {
-                if (upd.isCommand()) {
-                    List<String> cmd = upd.getSplitCommand();
-                    switch (cmd.get(0)) {
-                        case "/start":
-                            User user = User.fromUser(upd.getUser());
-                            user = Optional.ofNullable(userRepo.findOne(user.getId())).orElse(user);
-                            StateMachine<UserState, UserEvent> state = stateService.getStateFor(user);
-                            if (state.getState() == null) state.start();
-                            switch (state.getState().getId()) {
-                                case unknown:
-                                    stateService.setStateFor(user, state);
-                                    userRepo.save(user);
-
-                                    chatRepo.findByUserIdAndThenClose(user.getId());
+    private CommandService commandService;
 
-                                    Chat chat = new Chat();
-                                    chat.setUser(user);
-                                    chat.setType(ChatType.welcome);
-                                    StateMachine<WelcomeChatState, WelcomeChatEvent> chatState = stateService.getChatStateFor(chat);
-                                    chatState.start();
-                                    stateService.setChatStateFor(chat, chatState);
-                                    chatRepo.save(chat);
-                                    break;
-                            }
-
-                            break;
-                    }
-                } else {
-
-                }
-            }
+    @Autowired
+    private Accounted accounted;
+
+    private void handleUpdate(UpdateHelper upd) {
+        if (!upd.hasText()) throw new RuntimeException();
+
+        User user = User.fromUser(upd.getUser());
+        user = Optional.ofNullable(userRepo.findOne(user.getId())).orElse(user);
+        StateMachine<Accounted.State, Accounted.Event> state = stateService.getStateFor(user);
+        stateService.put(state, IChat.USER, user.getId());
+        stateService.put(state, upd);
+
+        if (state.getState() == null) {
+            userRepo.save(user);
+            chatRepo.findByUserIdAndThenClose(user.getId());
+            state.start();
+            stateService.setStateFor(user, state);
+            userRepo.save(user);
+        }
+        if (upd.isCommand())
+            commandService.handle(state);
+        else {
+            Optional<Chat> chat0 = chatRepo.findByUserIdAndStateThenActualize(user.getId());
+            chat0.ifPresent(c -> {
+                StateMachine chatState = stateService.getChatStateFor(c);
+                stateService.put(chatState, upd);
+                stateService.put(chatState, IChat.CHAT, c.getId());
+                IChat chat = context.getBean(c.getType().getBean());
+                chat.handle(chatState);
+            });
+            if (!chat0.isPresent())
+                accounted.handle(state);
         }
     }
 
     public void handleUpdate(Update upd) {
-        try {
-            handleUpdate(UpdateHelper.from(upd));
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
+        UpdateHelper helper = UpdateHelper.from(upd);
+        if (!helper.isEdit()) handleUpdate(helper);
     }
 
 }

+ 18 - 7
src/main/java/in/ocsf/these/days/app/state/ChatType.java

@@ -1,21 +1,32 @@
 package in.ocsf.these.days.app.state;/* kpmy 21.02.2017 */
 
 import com.fasterxml.jackson.core.type.TypeReference;
-import org.springframework.statemachine.StateMachine;
+import in.ocsf.these.days.app.state.chat.WelcomeChat;
+import in.ocsf.these.days.app.state.utils.PersistentStateMachineContext;
 
 public enum ChatType {
-    welcome("welcomeChatStateMachineFactory", new TypeReference<StateMachine<WelcomeChatState, WelcomeChatEvent>>() {
+    welcome((Class) WelcomeChat.class, "welcomeChatStateMachineFactory", new TypeReference<PersistentStateMachineContext<WelcomeChat.State, WelcomeChat.Event>>() {
     });
 
-    private String bean;
-    private TypeReference ref;
+    private String factoryBean;
+    private TypeReference persistenceRef;
+    private Class<IChat> bean;
 
-    ChatType(String bean, TypeReference ref) {
+    ChatType(Class<IChat> bean, String factoryBean, TypeReference persistenceRef) {
+        this.factoryBean = factoryBean;
+        this.persistenceRef = persistenceRef;
         this.bean = bean;
-        this.ref = ref;
     }
 
-    public String getBean() {
+    public Class<IChat> getBean() {
         return bean;
     }
+
+    public String getFactoryBean() {
+        return factoryBean;
+    }
+
+    public TypeReference getPersistenceRef() {
+        return persistenceRef;
+    }
 }

+ 20 - 0
src/main/java/in/ocsf/these/days/app/state/IChat.java

@@ -0,0 +1,20 @@
+package in.ocsf.these.days.app.state;/* kpmy 25.02.2017 */
+
+import org.springframework.statemachine.StateContext;
+import org.springframework.statemachine.StateMachine;
+
+public interface IChat<S, E> {
+    String CHAT = "CHAT";
+    String MESSAGE = "CHATMESSAGE";
+    String USER = "USER";
+
+    default <T> T get(StateMachine machine, String key) {
+        return machine.getExtendedState().get(key, (Class<T>) Object.class);
+    }
+
+    default <T> T get(StateContext context, String key) {
+        return context.getExtendedState().get(key, (Class<T>) Object.class);
+    }
+
+    void handle(StateMachine<S, E> state);
+}

+ 17 - 0
src/main/java/in/ocsf/these/days/app/state/IState.java

@@ -0,0 +1,17 @@
+package in.ocsf.these.days.app.state;/* kpmy 25.02.2017 */
+
+import in.ocsf.these.days.app.Ctx;
+import in.ocsf.these.days.app.state.utils.InvokeBeanMethodActionFactory;
+import org.springframework.statemachine.action.Action;
+
+public interface IState<S, E> {
+
+    default Action<S, E> getAction() {
+        InvokeBeanMethodActionFactory actionFactory = Ctx.getContext().getBean(InvokeBeanMethodActionFactory.class);
+        return actionFactory.newAction(getBean(), getValue());
+    }
+
+    String getValue();
+
+    Class getBean();
+}

+ 1 - 1
src/main/java/in/ocsf/these/days/app/state/InChatStateMachinePersist.java

@@ -14,6 +14,6 @@ public class InChatStateMachinePersist implements StateMachinePersist<Object, Ob
 
     @Override
     public StateMachineContext<Object, Object> read(Chat chat) throws Exception {
-        return fromMap(chat.getData());
+        return fromMap(chat.getData(), chat.getType().getPersistenceRef());
     }
 }

+ 46 - 20
src/main/java/in/ocsf/these/days/app/state/StateService.java

@@ -2,6 +2,8 @@ package in.ocsf.these.days.app.state;/* kpmy 20.02.2017 */
 
 import in.ocsf.these.days.app.object.Chat;
 import in.ocsf.these.days.app.object.User;
+import in.ocsf.these.days.app.state.account.AccountStateMachineFactoryConfig;
+import in.ocsf.these.days.app.state.account.Accounted;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.ApplicationContext;
@@ -15,43 +17,67 @@ import org.springframework.stereotype.Service;
 public class StateService {
 
     @Autowired
-    @Qualifier("userStateMachineFactory")
-    private StateMachineFactory<UserState, UserEvent> userStateMachineFactory;
+    @Qualifier("accountStateMachineFactory")
+    private StateMachineFactory<Accounted.State, Accounted.Event> accountStateMachineFactory;
 
     @Autowired
     private ApplicationContext context;
 
-    public StateMachine getChatStateFor(Chat chat) throws Exception {
+    public StateMachine getChatStateFor(Chat chat) {
         StateMachinePersister persist = new DefaultStateMachinePersister(new InChatStateMachinePersist());
-        StateMachineFactory factory = (StateMachineFactory) context.getBean(chat.getType().getBean());
+        StateMachineFactory factory = (StateMachineFactory) context.getBean(chat.getType().getFactoryBean());
         StateMachine ret = factory.getStateMachine(chat.getId());
 
-        if (chat.getData() != null)
-            ret = persist.restore(ret, chat);
-
-        return ret;
+        try {
+            if (chat.getData() != null)
+                ret = persist.restore(ret, chat);
+            return ret;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
     }
 
-    public void setChatStateFor(Chat chat, StateMachine state) throws Exception {
+    public void setChatStateFor(Chat chat, StateMachine state) {
         StateMachinePersister persist = new DefaultStateMachinePersister(new InChatStateMachinePersist());
-
-        persist.persist(state, chat);
+        try {
+            state.getExtendedState().getVariables().clear();
+            persist.persist(state, chat);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
     }
 
-    public StateMachine<UserState, UserEvent> getStateFor(User user) throws Exception {
-        StateMachinePersister<UserState, UserEvent, User> persist = new DefaultStateMachinePersister<>(new UserStateMachineFactoryConfig.InUserStateMachinePersist());
+    public StateMachine<Accounted.State, Accounted.Event> getStateFor(User user) {
+        StateMachinePersister<Accounted.State, Accounted.Event, User> persist = new DefaultStateMachinePersister<>(new AccountStateMachineFactoryConfig.InUserStateMachinePersist());
+
+        StateMachine<Accounted.State, Accounted.Event> ret = accountStateMachineFactory.getStateMachine(Long.toHexString(user.getId()));
 
-        StateMachine<UserState, UserEvent> ret = userStateMachineFactory.getStateMachine(Long.toHexString(user.getId()));
+        try {
+            if (user.getState() != null)
+                ret = persist.restore(ret, user);
 
-        if (user.getState() != null)
-            ret = persist.restore(ret, user);
+            return ret;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void setStateFor(User user, StateMachine<Accounted.State, Accounted.Event> state) {
+        StateMachinePersister<Accounted.State, Accounted.Event, User> persist = new DefaultStateMachinePersister<>(new AccountStateMachineFactoryConfig.InUserStateMachinePersist());
 
-        return ret;
+        try {
+            state.getExtendedState().getVariables().clear();
+            persist.persist(state, user);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
     }
 
-    public void setStateFor(User user, StateMachine<UserState, UserEvent> state) throws Exception {
-        StateMachinePersister<UserState, UserEvent, User> persist = new DefaultStateMachinePersister<>(new UserStateMachineFactoryConfig.InUserStateMachinePersist());
+    public void put(StateMachine state, Object value) {
+        put(state, IChat.MESSAGE, value);
+    }
 
-        persist.persist(state, user);
+    public void put(StateMachine state, String key, Object value) {
+        state.getExtendedState().getVariables().put(key, value);
     }
 }

+ 0 - 5
src/main/java/in/ocsf/these/days/app/state/UserEvent.java

@@ -1,5 +0,0 @@
-package in.ocsf.these.days.app.state;/* kpmy 20.02.2017 */
-
-public enum UserEvent {
-    start, init
-}

+ 0 - 5
src/main/java/in/ocsf/these/days/app/state/UserState.java

@@ -1,5 +0,0 @@
-package in.ocsf.these.days.app.state;/* kpmy 20.02.2017 */
-
-public enum UserState {
-    unknown, newbie, client
-}

+ 0 - 52
src/main/java/in/ocsf/these/days/app/state/UserStateMachineFactoryConfig.java

@@ -1,52 +0,0 @@
-package in.ocsf.these.days.app.state;/* kpmy 20.02.2017 */
-
-import in.ocsf.these.days.app.object.User;
-import in.ocsf.these.days.app.state.utils.BeanMapPersist;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.statemachine.StateMachineContext;
-import org.springframework.statemachine.StateMachinePersist;
-import org.springframework.statemachine.config.EnableStateMachineFactory;
-import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
-import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
-import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
-import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
-
-import java.util.EnumSet;
-
-@Configuration
-@EnableStateMachineFactory(name = "userStateMachineFactory")
-public class UserStateMachineFactoryConfig extends EnumStateMachineConfigurerAdapter<UserState, UserEvent> {
-
-    @Override
-    public void configure(StateMachineConfigurationConfigurer<UserState, UserEvent> config) throws Exception {
-        config.withConfiguration().autoStartup(false);
-    }
-
-    @Override
-    public void configure(StateMachineStateConfigurer<UserState, UserEvent> states) throws Exception {
-        states.withStates()
-                .initial(UserState.unknown)
-                .states(EnumSet.allOf(UserState.class));
-    }
-
-    @Override
-    public void configure(StateMachineTransitionConfigurer<UserState, UserEvent> transitions) throws Exception {
-        transitions.withExternal().source(UserState.unknown).target(UserState.newbie).event(UserEvent.start)
-                .and()
-                .withExternal().source(UserState.newbie).target(UserState.client).event(UserEvent.init);
-    }
-
-    public static class InUserStateMachinePersist implements StateMachinePersist<UserState, UserEvent, User>, BeanMapPersist {
-
-        @Override
-        public void write(StateMachineContext<UserState, UserEvent> context, User user) throws Exception {
-            user.setState(toMap(context));
-        }
-
-        @Override
-        public StateMachineContext<UserState, UserEvent> read(User user) throws Exception {
-            return fromMap(user.getState());
-        }
-    }
-
-}

+ 0 - 12
src/main/java/in/ocsf/these/days/app/state/WelcomeChat.java

@@ -1,12 +0,0 @@
-package in.ocsf.these.days.app.state;/* kpmy 22.02.2017 */
-
-import org.springframework.statemachine.StateContext;
-import org.springframework.stereotype.Service;
-
-@Service
-public class WelcomeChat {
-
-    public void hello(StateContext<WelcomeChatState, WelcomeChatEvent> ctx) {
-        throw new RuntimeException();
-    }
-}

+ 0 - 5
src/main/java/in/ocsf/these/days/app/state/WelcomeChatEvent.java

@@ -1,5 +0,0 @@
-package in.ocsf.these.days.app.state;/* kpmy 20.02.2017 */
-
-public enum WelcomeChatEvent {
-    reply, answer
-}

+ 0 - 5
src/main/java/in/ocsf/these/days/app/state/WelcomeChatState.java

@@ -1,5 +0,0 @@
-package in.ocsf.these.days.app.state;/* kpmy 20.02.2017 */
-
-public enum WelcomeChatState {
-    hello, ask, goodbye
-}

+ 55 - 0
src/main/java/in/ocsf/these/days/app/state/account/AccountStateMachineFactoryConfig.java

@@ -0,0 +1,55 @@
+package in.ocsf.these.days.app.state.account;/* kpmy 20.02.2017 */
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import in.ocsf.these.days.app.object.User;
+import in.ocsf.these.days.app.state.utils.BeanMapPersist;
+import in.ocsf.these.days.app.state.utils.PersistentStateMachineContext;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.statemachine.StateMachineContext;
+import org.springframework.statemachine.StateMachinePersist;
+import org.springframework.statemachine.config.EnableStateMachineFactory;
+import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
+import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
+
+import java.util.EnumSet;
+
+@Configuration
+@EnableStateMachineFactory(name = "accountStateMachineFactory")
+public class AccountStateMachineFactoryConfig extends EnumStateMachineConfigurerAdapter<Accounted.State, Accounted.Event> {
+
+    @Override
+    public void configure(StateMachineConfigurationConfigurer<Accounted.State, Accounted.Event> config) throws Exception {
+        config.withConfiguration().autoStartup(false);
+    }
+
+    @Override
+    public void configure(StateMachineStateConfigurer<Accounted.State, Accounted.Event> states) throws Exception {
+        states.withStates()
+                .initial(Accounted.State.unknown, Accounted.State.unknown.getAction())
+                .states(EnumSet.allOf(Accounted.State.class));
+    }
+
+    @Override
+    public void configure(StateMachineTransitionConfigurer<Accounted.State, Accounted.Event> transitions) throws Exception {
+        transitions.withExternal().source(Accounted.State.unknown).target(Accounted.State.newbie).event(Accounted.Event.start).action(Accounted.State.newbie.getAction())
+                .and()
+                .withExternal().source(Accounted.State.newbie).target(Accounted.State.client).event(Accounted.Event.init).action(Accounted.State.client.getAction());
+    }
+
+    public static class InUserStateMachinePersist implements StateMachinePersist<Accounted.State, Accounted.Event, User>, BeanMapPersist {
+
+        @Override
+        public void write(StateMachineContext<Accounted.State, Accounted.Event> context, User user) throws Exception {
+            user.setState(toMap(context));
+        }
+
+        @Override
+        public StateMachineContext<Accounted.State, Accounted.Event> read(User user) throws Exception {
+            return fromMap(user.getState(), new TypeReference<PersistentStateMachineContext<Accounted.State, Accounted.Event>>() {
+            });
+        }
+    }
+
+}

+ 83 - 0
src/main/java/in/ocsf/these/days/app/state/account/Accounted.java

@@ -0,0 +1,83 @@
+package in.ocsf.these.days.app.state.account;/* kpmy 25.02.2017 */
+
+import in.ocsf.these.days.app.object.User;
+import in.ocsf.these.days.app.repo.UserRepository;
+import in.ocsf.these.days.app.state.IChat;
+import in.ocsf.these.days.app.state.IState;
+import in.ocsf.these.days.app.state.StateService;
+import org.apache.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.statemachine.StateContext;
+import org.springframework.statemachine.StateMachine;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class Accounted implements IChat<Accounted.State, Accounted.Event> {
+
+    private final Logger log = Logger.getLogger(getClass());
+
+    @Autowired
+    private UserRepository userRepo;
+
+    @Autowired
+    private StateService stateService;
+
+    public void unknown(StateContext<State, Event> ctx) {
+        //do nothing
+    }
+
+    public void newbie(StateContext<State, Event> ctx) {
+        log.info("newbie");
+    }
+
+    public void client(StateContext<State, Event> ctx) {
+
+    }
+
+    public void handleCommand(List<String> cmd, StateMachine<State, Event> state0) {
+        switch (cmd.get(0)) {
+            case "/start":
+                switch (state0.getState().getId()) {
+                    case unknown:
+                        User user = userRepo.findOne((Long) get(state0, USER));
+                        StateMachine<State, Event> state = stateService.getStateFor(user);
+                        stateService.put(state, get(state0, MESSAGE));
+                        state.sendEvent(Event.start);
+                        stateService.setStateFor(user, state);
+                        userRepo.save(user);
+                        break;
+                    default:
+                        // throw new RuntimeException();
+                }
+                break;
+            default:
+                // throw new RuntimeException();
+        }
+    }
+
+    @Override
+    public void handle(StateMachine<State, Event> state) {
+
+    }
+
+
+    public enum Event {
+        start, init
+    }
+
+    public enum State implements IState<State, Event> {
+        unknown, newbie, client;
+
+        @Override
+        public String getValue() {
+            return name();
+        }
+
+        @Override
+        public Class getBean() {
+            return Accounted.class;
+        }
+    }
+}

+ 58 - 0
src/main/java/in/ocsf/these/days/app/state/chat/WelcomeChat.java

@@ -0,0 +1,58 @@
+package in.ocsf.these.days.app.state.chat;/* kpmy 22.02.2017 */
+
+import in.ocsf.these.days.app.messaging.ChatHelper;
+import in.ocsf.these.days.app.messaging.UpdateHelper;
+import in.ocsf.these.days.app.state.IChat;
+import in.ocsf.these.days.app.state.IState;
+import in.ocsf.these.days.app.state.utils.MessageTemplate;
+import org.apache.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.statemachine.StateContext;
+import org.springframework.statemachine.StateMachine;
+import org.springframework.stereotype.Service;
+
+@Service
+public class WelcomeChat implements IChat<WelcomeChat.State, WelcomeChat.Event> {
+
+    private final Logger log = Logger.getLogger(getClass());
+
+    private final MessageTemplate helloMsg = MessageTemplate.from("Привет.");
+
+    @Autowired
+    private ChatHelper chatHelper;
+
+    public void hello(StateContext<State, Event> ctx) {
+        UpdateHelper upd = ctx.getExtendedState().get(IChat.MESSAGE, UpdateHelper.class);
+        chatHelper.sendSimpleTextMessage(upd.getChatId(), helloMsg.render());
+    }
+
+    public void ask(StateContext<State, Event> ctx) {
+        UpdateHelper upd = ctx.getExtendedState().get(IChat.MESSAGE, UpdateHelper.class);
+        chatHelper.sendSimpleTextMessage(upd.getChatId(), "Как дела?");
+    }
+
+    public void goodbye(StateContext<State, Event> ctx) {
+        log.info("hello");
+    }
+
+    @Override
+    public void handle(StateMachine<State, Event> state) {
+
+    }
+
+    public enum Event {
+        reply, answer
+    }
+
+    public enum State implements IState<State, Event> {
+        hello, ask, goodbye;
+
+        public String getValue() {
+            return name();
+        }
+
+        public Class getBean() {
+            return WelcomeChat.class;
+        }
+    }
+}

+ 9 - 14
src/main/java/in/ocsf/these/days/app/state/WelcomeChatStateMachineFactoryConfig.java → src/main/java/in/ocsf/these/days/app/state/chat/WelcomeChatStateMachineFactoryConfig.java

@@ -1,7 +1,5 @@
-package in.ocsf.these.days.app.state;/* kpmy 20.02.2017 */
+package in.ocsf.these.days.app.state.chat;/* kpmy 20.02.2017 */
 
-import in.ocsf.these.days.app.state.utils.InvokeBeanMethodActionFactory;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.statemachine.config.EnableStateMachineFactory;
 import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
@@ -13,27 +11,24 @@ import java.util.EnumSet;
 
 @Configuration
 @EnableStateMachineFactory(name = "welcomeChatStateMachineFactory")
-public class WelcomeChatStateMachineFactoryConfig extends EnumStateMachineConfigurerAdapter<WelcomeChatState, WelcomeChatEvent> {
-
-    @Autowired
-    private InvokeBeanMethodActionFactory actionFactory;
+public class WelcomeChatStateMachineFactoryConfig extends EnumStateMachineConfigurerAdapter<WelcomeChat.State, WelcomeChat.Event> {
 
     @Override
-    public void configure(StateMachineConfigurationConfigurer<WelcomeChatState, WelcomeChatEvent> config) throws Exception {
+    public void configure(StateMachineConfigurationConfigurer<WelcomeChat.State, WelcomeChat.Event> config) throws Exception {
         config.withConfiguration().autoStartup(false);
     }
 
     @Override
-    public void configure(StateMachineStateConfigurer<WelcomeChatState, WelcomeChatEvent> states) throws Exception {
+    public void configure(StateMachineStateConfigurer<WelcomeChat.State, WelcomeChat.Event> states) throws Exception {
         states.withStates()
-                .initial(WelcomeChatState.hello, actionFactory.newAction(WelcomeChat.class, "hello"))
-                .states(EnumSet.allOf(WelcomeChatState.class));
+                .initial(WelcomeChat.State.hello, WelcomeChat.State.hello.getAction())
+                .states(EnumSet.allOf(WelcomeChat.State.class));
     }
 
     @Override
-    public void configure(StateMachineTransitionConfigurer<WelcomeChatState, WelcomeChatEvent> transitions) throws Exception {
-        transitions.withExternal().source(WelcomeChatState.hello).target(WelcomeChatState.ask).event(WelcomeChatEvent.reply)
+    public void configure(StateMachineTransitionConfigurer<WelcomeChat.State, WelcomeChat.Event> transitions) throws Exception {
+        transitions.withExternal().source(WelcomeChat.State.hello).target(WelcomeChat.State.ask).event(WelcomeChat.Event.reply).action(WelcomeChat.State.ask.getAction())
                 .and()
-                .withExternal().source(WelcomeChatState.ask).target(WelcomeChatState.goodbye).event(WelcomeChatEvent.answer);
+                .withExternal().source(WelcomeChat.State.ask).target(WelcomeChat.State.goodbye).event(WelcomeChat.Event.answer).action(WelcomeChat.State.goodbye.getAction());
     }
 }

+ 9 - 10
src/main/java/in/ocsf/these/days/app/state/utils/BeanMapPersist.java

@@ -1,25 +1,24 @@
 package in.ocsf.these.days.app.state.utils;/* kpmy 20.02.2017 */
 
-import org.apache.commons.beanutils.BeanMap;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import org.springframework.statemachine.StateMachineContext;
 
-import java.util.HashMap;
 import java.util.Map;
 
 public interface BeanMapPersist {
 
     default Map<String, Object> toMap(StateMachineContext context) throws Exception {
-        Map<String, Object> data = new HashMap<>(new BeanMap(context));
-        data.replace("class", ((Class) data.get("class")).getCanonicalName());
-        return data;
+        ObjectMapper oMapper = new ObjectMapper();
+        Map<String, Object> map = oMapper.convertValue(context, Map.class);
+        map.remove("extendedState");
+        return map;
     }
 
-    default StateMachineContext fromMap(Map<String, Object> data) throws Exception {
+    default StateMachineContext fromMap(Map<String, Object> data, TypeReference ref) throws Exception {
         if (data != null) {
-            data.replace("class", Class.forName((String) data.get("class")));
-            BeanMap beanMap = new BeanMap();
-            beanMap.putAll(data);
-            return (StateMachineContext) beanMap.getBean();
+            ObjectMapper oMapper = new ObjectMapper();
+            return oMapper.convertValue(data, ref);
         } else {
             return null;
         }

+ 41 - 0
src/main/java/in/ocsf/these/days/app/state/utils/MessageTemplate.java

@@ -0,0 +1,41 @@
+package in.ocsf.these.days.app.state.utils;/* kpmy 25.02.2017 */
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MessageTemplate {
+
+    private Mustache mustache;
+
+    private MessageTemplate() {
+
+    }
+
+    public static MessageTemplate from(String string) {
+        MessageTemplate template = new MessageTemplate();
+        template.mustache = new DefaultMustacheFactory().compile(new StringReader(string), Long.toHexString(new Date().getTime()));
+        return template;
+    }
+
+    public String render() {
+        return render(new HashMap<>());
+    }
+
+    public String render(Map<String, Object> data) {
+        Writer wr = new StringWriter();
+        mustache.execute(wr, data);
+        try {
+            wr.flush();
+            return wr.toString();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 10 - 0
src/main/java/in/ocsf/these/days/app/state/utils/PersistentStateMachineContext.java

@@ -0,0 +1,10 @@
+package in.ocsf.these.days.app.state.utils;/* kpmy 25.02.2017 */
+
+import org.springframework.statemachine.support.DefaultStateMachineContext;
+
+public class PersistentStateMachineContext<S, E> extends DefaultStateMachineContext<S, E> {
+
+    public PersistentStateMachineContext() {
+        super(null, null, null, null);
+    }
+}