Jelajahi Sumber

экшны стейт-машины с такой-то матерью заработали

kpmy 8 tahun lalu
induk
melakukan
a88bf525a1

+ 68 - 0
src/main/java/in/ocsf/these/days/app/object/Chat.java

@@ -0,0 +1,68 @@
+package in.ocsf.these.days.app.object;/* kpmy 21.02.2017 */
+
+import in.ocsf.these.days.app.state.ChatType;
+import org.bson.types.ObjectId;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.DBRef;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.Date;
+import java.util.Map;
+
+@Document(collection = "chat")
+public class Chat {
+
+    @Id
+    private ObjectId id = new ObjectId();
+
+    private Date started = new Date();
+
+    @DBRef(lazy = true)
+    private User user;
+
+    private Map<String, Object> data;
+
+    private ChatType type;
+
+    private ChatState state = ChatState.open;
+
+    public Date getStarted() {
+        return started;
+    }
+
+    public User getUser() {
+        return user;
+    }
+
+    public void setUser(User user) {
+        this.user = user;
+    }
+
+    public Map<String, Object> getData() {
+        return data;
+    }
+
+    public void setData(Map<String, Object> data) {
+        this.data = data;
+    }
+
+    public ChatState getState() {
+        return state;
+    }
+
+    public void setState(ChatState state) {
+        this.state = state;
+    }
+
+    public ChatType getType() {
+        return type;
+    }
+
+    public void setType(ChatType type) {
+        this.type = type;
+    }
+
+    public String getId() {
+        return id.toHexString();
+    }
+}

+ 5 - 0
src/main/java/in/ocsf/these/days/app/object/ChatState.java

@@ -0,0 +1,5 @@
+package in.ocsf.these.days.app.object;/* kpmy 21.02.2017 */
+
+public enum ChatState {
+    open, closed
+}

+ 14 - 0
src/main/java/in/ocsf/these/days/app/repo/ChatRepository.java

@@ -0,0 +1,14 @@
+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 org.bson.types.ObjectId;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.data.rest.core.annotation.RepositoryRestResource;
+
+import java.util.List;
+
+@RepositoryRestResource(exported = false)
+public interface ChatRepository extends MongoRepository<Chat, ObjectId>, ChatRepositoryCustom {
+    List<Chat> findByUserIdAndState(Long id, ChatState state);
+}

+ 12 - 0
src/main/java/in/ocsf/these/days/app/repo/ChatRepositoryCustom.java

@@ -0,0 +1,12 @@
+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;
+
+public interface ChatRepositoryCustom {
+    List<Chat> findByUserIdAndStateThenActualize(Long id, ChatState state);
+
+    void findByUserIdAndThenClose(Long id);
+}

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

@@ -0,0 +1,40 @@
+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 org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ChatRepositoryImpl implements ChatRepositoryCustom {
+
+    @Autowired
+    @Lazy
+    private ChatRepository chatRepo;
+
+    @Override
+    public List<Chat> findByUserIdAndStateThenActualize(Long id, ChatState state) {
+        List<Chat> cl = chatRepo.findByUserIdAndState(id, state);
+        LocalDateTime now = LocalDateTime.now();
+        for (Chat c : cl) {
+            LocalDateTime cd = LocalDateTime.ofInstant(c.getStarted().toInstant(), ZoneId.systemDefault());
+            if (cd.isBefore(now.minusHours(1))) {
+                c.setState(ChatState.closed);
+                chatRepo.save(c);
+            }
+        }
+        return cl.stream().filter(c -> c.getState().equals(ChatState.open)).collect(Collectors.toList());
+    }
+
+    @Override
+    public void findByUserIdAndThenClose(Long id) {
+        for (Chat c : chatRepo.findByUserIdAndState(id, ChatState.open)) {
+            c.setState(ChatState.closed);
+            chatRepo.save(c);
+        }
+    }
+}

+ 25 - 6
src/main/java/in/ocsf/these/days/app/service/UpdateService.java

@@ -3,18 +3,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.UserEvent;
-import in.ocsf.these.days.app.state.UserState;
+import in.ocsf.these.days.app.state.*;
 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;
+import java.util.Optional;
 
 @Service
 public class UpdateService {
@@ -33,6 +35,9 @@ public class UpdateService {
     @Autowired
     private StateService stateService;
 
+    @Autowired
+    private ChatRepository chatRepo;
+
     @Autowired
     private ChatHelper chat;
 
@@ -44,12 +49,26 @@ public class UpdateService {
                     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().getId().equals(UserState.unknown)) {
-                                stateService.setStateFor(user, state);
-                                chat.sendSimpleTextMessage(upd.getChatId(), "дратути...");
-                                userRepo.save(user);
+                            if (state.getState() == null) state.start();
+                            switch (state.getState().getId()) {
+                                case unknown:
+                                    stateService.setStateFor(user, state);
+                                    userRepo.save(user);
+
+                                    chatRepo.findByUserIdAndThenClose(user.getId());
+
+                                    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 {

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

@@ -0,0 +1,21 @@
+package in.ocsf.these.days.app.state;/* kpmy 21.02.2017 */
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.springframework.statemachine.StateMachine;
+
+public enum ChatType {
+    welcome("welcomeChatStateMachineFactory", new TypeReference<StateMachine<WelcomeChatState, WelcomeChatEvent>>() {
+    });
+
+    private String bean;
+    private TypeReference ref;
+
+    ChatType(String bean, TypeReference ref) {
+        this.bean = bean;
+        this.ref = ref;
+    }
+
+    public String getBean() {
+        return bean;
+    }
+}

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

@@ -0,0 +1,19 @@
+package in.ocsf.these.days.app.state;/* kpmy 21.02.2017 */
+
+import in.ocsf.these.days.app.object.Chat;
+import in.ocsf.these.days.app.state.utils.BeanMapPersist;
+import org.springframework.statemachine.StateMachineContext;
+import org.springframework.statemachine.StateMachinePersist;
+
+public class InChatStateMachinePersist implements StateMachinePersist<Object, Object, Chat>, BeanMapPersist {
+
+    @Override
+    public void write(StateMachineContext<Object, Object> context, Chat chat) throws Exception {
+        chat.setData(toMap(context));
+    }
+
+    @Override
+    public StateMachineContext<Object, Object> read(Chat chat) throws Exception {
+        return fromMap(chat.getData());
+    }
+}

+ 23 - 5
src/main/java/in/ocsf/these/days/app/service/StateService.java → src/main/java/in/ocsf/these/days/app/state/StateService.java

@@ -1,11 +1,10 @@
-package in.ocsf.these.days.app.service;/* kpmy 20.02.2017 */
+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.UserEvent;
-import in.ocsf.these.days.app.state.UserState;
-import in.ocsf.these.days.app.state.UserStateMachineFactoryConfig;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.ApplicationContext;
 import org.springframework.statemachine.StateMachine;
 import org.springframework.statemachine.config.StateMachineFactory;
 import org.springframework.statemachine.persist.DefaultStateMachinePersister;
@@ -19,11 +18,30 @@ public class StateService {
     @Qualifier("userStateMachineFactory")
     private StateMachineFactory<UserState, UserEvent> userStateMachineFactory;
 
+    @Autowired
+    private ApplicationContext context;
+
+    public StateMachine getChatStateFor(Chat chat) throws Exception {
+        StateMachinePersister persist = new DefaultStateMachinePersister(new InChatStateMachinePersist());
+        StateMachineFactory factory = (StateMachineFactory) context.getBean(chat.getType().getBean());
+        StateMachine ret = factory.getStateMachine(chat.getId());
+
+        if (chat.getData() != null)
+            ret = persist.restore(ret, chat);
+
+        return ret;
+    }
+
+    public void setChatStateFor(Chat chat, StateMachine state) throws Exception {
+        StateMachinePersister persist = new DefaultStateMachinePersister(new InChatStateMachinePersist());
+
+        persist.persist(state, chat);
+    }
+
     public StateMachine<UserState, UserEvent> getStateFor(User user) throws Exception {
         StateMachinePersister<UserState, UserEvent, User> persist = new DefaultStateMachinePersister<>(new UserStateMachineFactoryConfig.InUserStateMachinePersist());
 
         StateMachine<UserState, UserEvent> ret = userStateMachineFactory.getStateMachine(Long.toHexString(user.getId()));
-        ret.start();
 
         if (user.getState() != null)
             ret = persist.restore(ret, user);

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

@@ -1,11 +1,13 @@
 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;
 
@@ -15,6 +17,11 @@ import java.util.EnumSet;
 @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()

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

@@ -0,0 +1,12 @@
+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();
+    }
+}

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

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

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

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

+ 19 - 4
src/main/java/in/ocsf/these/days/app/state/WelcomeChatStateMachineFactoryConfig.java

@@ -1,24 +1,39 @@
 package in.ocsf.these.days.app.state;/* 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;
+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 = "welcomeChatStateMachineFactory")
+@Configuration
+@EnableStateMachineFactory(name = "welcomeChatStateMachineFactory")
 public class WelcomeChatStateMachineFactoryConfig extends EnumStateMachineConfigurerAdapter<WelcomeChatState, WelcomeChatEvent> {
 
+    @Autowired
+    private InvokeBeanMethodActionFactory actionFactory;
+
+    @Override
+    public void configure(StateMachineConfigurationConfigurer<WelcomeChatState, WelcomeChatEvent> config) throws Exception {
+        config.withConfiguration().autoStartup(false);
+    }
+
     @Override
     public void configure(StateMachineStateConfigurer<WelcomeChatState, WelcomeChatEvent> states) throws Exception {
         states.withStates()
-                .initial(WelcomeChatState.hello)
+                .initial(WelcomeChatState.hello, actionFactory.newAction(WelcomeChat.class, "hello"))
                 .states(EnumSet.allOf(WelcomeChatState.class));
     }
 
     @Override
     public void configure(StateMachineTransitionConfigurer<WelcomeChatState, WelcomeChatEvent> transitions) throws Exception {
-        transitions.withExternal().source(WelcomeChatState.hello).target(WelcomeChatState.goodbye).event(WelcomeChatEvent.reply);
+        transitions.withExternal().source(WelcomeChatState.hello).target(WelcomeChatState.ask).event(WelcomeChatEvent.reply)
+                .and()
+                .withExternal().source(WelcomeChatState.ask).target(WelcomeChatState.goodbye).event(WelcomeChatEvent.answer);
     }
 }

+ 1 - 1
src/main/java/in/ocsf/these/days/app/state/BeanMapPersist.java → src/main/java/in/ocsf/these/days/app/state/utils/BeanMapPersist.java

@@ -1,4 +1,4 @@
-package in.ocsf.these.days.app.state;/* kpmy 20.02.2017 */
+package in.ocsf.these.days.app.state.utils;/* kpmy 20.02.2017 */
 
 import org.apache.commons.beanutils.BeanMap;
 import org.springframework.statemachine.StateMachineContext;

+ 38 - 0
src/main/java/in/ocsf/these/days/app/state/utils/InvokeBeanMethodAction.java

@@ -0,0 +1,38 @@
+package in.ocsf.these.days.app.state.utils;/* kpmy 22.02.2017 */
+
+import org.springframework.beans.factory.config.MethodInvokingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.statemachine.StateContext;
+import org.springframework.statemachine.action.Action;
+
+public class InvokeBeanMethodAction<S, E> implements Action<S, E> {
+
+    private Class clazz;
+    private String methodName;
+    private ApplicationContext ctx;
+
+    private InvokeBeanMethodAction() {
+
+    }
+
+    public InvokeBeanMethodAction(ApplicationContext ctx, Class clazz, String methodName) {
+        this.clazz = clazz;
+        this.methodName = methodName;
+        this.ctx = ctx;
+    }
+
+    @Override
+    public void execute(StateContext<S, E> context) {
+        MethodInvokingBean invokingBean = new MethodInvokingBean();
+        invokingBean.setTargetClass(clazz);
+        invokingBean.setTargetObject(ctx.getBean(clazz));
+        invokingBean.setTargetMethod(methodName);
+        invokingBean.setArguments(new Object[]{context});
+        try {
+            invokingBean.prepare();
+            invokingBean.invoke();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 16 - 0
src/main/java/in/ocsf/these/days/app/state/utils/InvokeBeanMethodActionFactory.java

@@ -0,0 +1,16 @@
+package in.ocsf.these.days.app.state.utils;/* kpmy 22.02.2017 */
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Service;
+
+@Service
+public class InvokeBeanMethodActionFactory {
+
+    @Autowired
+    private ApplicationContext context;
+
+    public <S, E> InvokeBeanMethodAction<S, E> newAction(Class clazz, String methodName) {
+        return new InvokeBeanMethodAction<S, E>(context, clazz, methodName);
+    }
+}