Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.appform.statesman.engine;

import io.appform.statesman.model.MessageConfig;
import io.appform.statesman.model.action.template.ActionTemplate;

import java.util.Optional;

public interface MessageConfigStore {

Optional<MessageConfig> create(MessageConfig messageConfig);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should create return optional ? if it fails, it should throw the exception right rather than fail internally ?


Optional<MessageConfig> get(String messageConfigId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.appform.statesman.model;

import com.fasterxml.jackson.databind.JsonNode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotNull;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MessageConfig {
@NotNull
String messageId;

@NotNull
JsonNode messageBody;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets mark them private

}
6 changes: 6 additions & 0 deletions statesman-server/db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,9 @@ CREATE TABLE `workflow_templates` (
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_template_id` (`template_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `message_config` (
`message_id` varchar(64) DEFAULT NULL,
`message_config_body` blob DEFAULT NULL,
PRIMARY KEY (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.appform.statesman.server.dao.message;

public interface IMessageConstructor {
String constructMessage(String messageId,String language,String state);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.appform.statesman.server.dao.message;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import io.appform.dropwizard.sharding.dao.LookupDao;
import io.appform.statesman.engine.MessageConfigStore;
import io.appform.statesman.model.MessageConfig;
import io.appform.statesman.model.exception.ResponseCode;
import io.appform.statesman.model.exception.StatesmanError;
import io.appform.statesman.server.utils.MapperUtils;
import io.appform.statesman.server.utils.WorkflowUtils;
import lombok.extern.slf4j.Slf4j;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Slf4j
@Singleton
public class MessageConfigStoreCommand implements MessageConfigStore {

private final LookupDao<StoredMessageConfig> messageLookupDao;
private final LoadingCache<String, Optional<MessageConfig>> messageConfigCache;

@Inject
public MessageConfigStoreCommand(LookupDao<StoredMessageConfig> messageLookupDao) {
this.messageLookupDao = messageLookupDao;
messageConfigCache = Caffeine.newBuilder()
.maximumSize(1_000)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does 1_000 mean ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a digit seperator notion that I took from other classes. For grouping digits for better readability

.expireAfterWrite(300, TimeUnit.SECONDS)
.refreshAfterWrite(60, TimeUnit.SECONDS)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whats expire after write ? also refresh can be slower and 15 mins should be fine IMO

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expire after write is basically looks at last write time and expires the entry after specified time, we can increase it and sure I will increase the refresh time

.build(key -> {
log.debug("Loading data for action for key: {}", key);
return getFromDb(key);
});
}

private Optional<MessageConfig> getFromDb(String messageId) {
try {
return messageLookupDao.get(messageId)
.map(config ->
new MessageConfig(config.getMessageId(), MapperUtils.readTree(config.getMessageConfigBody())));
} catch (Exception e) {
throw StatesmanError.propagate(e, ResponseCode.DAO_ERROR);
}
}


@Override
public Optional<MessageConfig> create(MessageConfig messageConfig) {
try {
return messageLookupDao
.save(WorkflowUtils.toDao(messageConfig))
.map(config ->
new MessageConfig(config.getMessageId(), MapperUtils.readTree(config.getMessageConfigBody())));

} catch (Exception e) {
throw StatesmanError.propagate(e, ResponseCode.DAO_ERROR);
}
}

@Override
public Optional<MessageConfig> get(String messageConfigId) {
try {
return messageConfigCache.get(messageConfigId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how are we pre populating data ? should we fall back to DB if cache miss occurs ? because ideally we don't expect to query for a msg id which doesn't exist. so either msg is present in cache or in DB.

or are we pre populating the DB somehow ? in that case how will new writes get cached ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this method called getFromDb which is hooked into caffine while building the cache, so any cache miss will lead to a DB call.

} catch (Exception e) {
throw StatesmanError.propagate(e, ResponseCode.DAO_ERROR);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.appform.statesman.server.dao.message;

import com.fasterxml.jackson.databind.JsonNode;
import io.appform.statesman.model.MessageConfig;
import io.appform.statesman.model.exception.ResponseCode;
import io.appform.statesman.model.exception.StatesmanError;
import io.appform.statesman.server.utils.MapperUtils;
import lombok.extern.slf4j.Slf4j;

import javax.inject.Inject;
import java.util.Optional;


@Slf4j
public class MessageConstructor implements IMessageConstructor {

private final MessageConfigStoreCommand messageConfigStoreCommand;
private final String DEFAULT_FILED_NAME = "default";

@Inject
public MessageConstructor(MessageConfigStoreCommand messageConfigStoreCommand) {
this.messageConfigStoreCommand = messageConfigStoreCommand;
}


@Override
public String constructMessage(String messageId, String language, String state) {

Optional<MessageConfig> optionalMessageConfig = messageConfigStoreCommand.get(messageId);
if(!optionalMessageConfig.isPresent()){
throw new StatesmanError("No message config found", ResponseCode.NO_PROVIDER_FOUND);
}

MessageConfig storeOutput = optionalMessageConfig.get();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a comment of the json structure ? will help whoever will read this code in future

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

JsonNode root = storeOutput.getMessageBody();
JsonNode languageNode;

if(root == null){
throw new StatesmanError("No message config found", ResponseCode.NO_PROVIDER_FOUND);
}else if((root.get(language) == null)){
if(root.get(DEFAULT_FILED_NAME) == null) {
throw new StatesmanError("No default language found", ResponseCode.NO_PROVIDER_FOUND);
}else{
languageNode = root.get(DEFAULT_FILED_NAME);
}
}else{
languageNode = root.get(language);
}

if(languageNode.get(state) != null) {
return languageNode.get(state).asText();
}else if(languageNode.get(DEFAULT_FILED_NAME) != null) {
return languageNode.get(DEFAULT_FILED_NAME).asText();
}else{
return root.get(DEFAULT_FILED_NAME).get(DEFAULT_FILED_NAME).asText();
}
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.appform.statesman.server.dao.message;

import io.appform.dropwizard.sharding.sharding.LookupKey;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Entity
@Table(name = "message_config", uniqueConstraints = {
@UniqueConstraint(columnNames = "message_id")
})
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class StoredMessageConfig {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we merge this with MessageConfig ?


@Id
@Column(name = "message_id")
@LookupKey
private String messageId;

@Column(name = "message_config_body")
private byte[] messageConfigBody;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.appform.dropwizard.sharding.dao.RelationalDao;
import io.appform.statesman.server.dao.action.StoredActionTemplate;
import io.appform.statesman.server.dao.callback.StoredCallbackTransformationTemplate;
import io.appform.statesman.server.dao.message.StoredMessageConfig;
import io.appform.statesman.server.dao.providers.StoredProvider;
import io.appform.statesman.server.dao.transition.StoredStateTransition;
import io.appform.statesman.server.dao.workflow.StoredWorkflowInstance;
Expand All @@ -34,6 +35,12 @@ public LookupDao<StoredActionTemplate> provideActionTemplateLookupDao() {
return dbShardingBundle.createParentObjectDao(StoredActionTemplate.class);
}

@Singleton
@Provides
public LookupDao<StoredMessageConfig> provideMessageLookupDao() {
return dbShardingBundle.createParentObjectDao(StoredMessageConfig.class);
}

@Singleton
@Provides
public LookupDao<StoredWorkflowTemplate> provideWorkflowTemplateLookupDao() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import io.appform.statesman.engine.ActionTemplateStore;
import io.appform.statesman.engine.ProviderSelector;
import io.appform.statesman.engine.TransitionStore;
import io.appform.statesman.engine.WorkflowProvider;
import io.appform.statesman.engine.*;
import io.appform.statesman.engine.action.ActionExecutor;
import io.appform.statesman.engine.action.ActionExecutorImpl;
import io.appform.statesman.engine.action.ActionRegistry;
Expand All @@ -30,6 +27,9 @@
import io.appform.statesman.server.dao.action.ActionTemplateStoreCommand;
import io.appform.statesman.server.dao.callback.CallbackTemplateProvider;
import io.appform.statesman.server.dao.callback.CallbackTemplateProviderCommand;
import io.appform.statesman.server.dao.message.IMessageConstructor;
import io.appform.statesman.server.dao.message.MessageConfigStoreCommand;
import io.appform.statesman.server.dao.message.MessageConstructor;
import io.appform.statesman.server.dao.transition.TransitionStoreCommand;
import io.appform.statesman.server.dao.workflow.WorkflowProviderCommand;
import io.appform.statesman.server.droppedcalldetector.DroppedCallDetector;
Expand All @@ -46,6 +46,7 @@ public class StatesmanModule extends AbstractModule {
@Override
protected void configure() {
bind(ActionTemplateStore.class).to(ActionTemplateStoreCommand.class);
bind(MessageConfigStore.class).to(MessageConfigStoreCommand.class);
bind(TransitionStore.class).to(TransitionStoreCommand.class);
bind(WorkflowProvider.class).to(WorkflowProviderCommand.class);
bind(CallbackTemplateProvider.class).to(CallbackTemplateProviderCommand.class);
Expand All @@ -58,6 +59,7 @@ protected void configure() {
.annotatedWith(Names.named("foxtrotEventSender"))
.to(FoxtrotEventSender.class);
bind(IdExtractor.class).to(CompoundIdExtractor.class);
bind(IMessageConstructor.class).to(MessageConstructor.class);
}

@Singleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.appform.statesman.engine.ActionTemplateStore;
import io.appform.statesman.engine.MessageConfigStore;
import io.appform.statesman.engine.TransitionStore;
import io.appform.statesman.engine.WorkflowProvider;
import io.appform.statesman.model.MessageConfig;
import io.appform.statesman.model.StateTransition;
import io.appform.statesman.model.WorkflowTemplate;
import io.appform.statesman.model.action.template.ActionTemplate;
import io.appform.statesman.server.dao.message.IMessageConstructor;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -33,14 +36,20 @@ public class TemplateResource {
private final ActionTemplateStore actionTemplateStore;
private final TransitionStore transitionStore;
private final WorkflowProvider workflowProvider;
private final MessageConfigStore messageConfigStore;
private final IMessageConstructor messageConstrutor;

@Inject
public TemplateResource(final ActionTemplateStore actionTemplateStore,
final TransitionStore transitionStore,
final WorkflowProvider workflowProvider) {
final WorkflowProvider workflowProvider,
final MessageConfigStore messageConfigStore,
final IMessageConstructor messageConstrutor) {
this.actionTemplateStore = actionTemplateStore;
this.transitionStore = transitionStore;
this.workflowProvider = workflowProvider;
this.messageConfigStore = messageConfigStore;
this.messageConstrutor = messageConstrutor;
}


Expand Down Expand Up @@ -222,4 +231,32 @@ public Response updateAction(@Valid ActionTemplate actionTemplate) {
.build();
}

@POST
@Timed
@Path("/messageconfig")
@ApiOperation("Create Message Config")
public Response createMessageConfig(@Valid MessageConfig messageConfig) {
Optional<MessageConfig> messageConfigOptional = messageConfigStore.create(messageConfig);
if (!messageConfigOptional.isPresent()) {
return Response.serverError()
.build();
}
return Response.ok()
.entity(messageConfigOptional.get())
.build();
}

@GET
@Timed
@Path("/messageconfig/{messageId}")
@ApiOperation("Get message config")
public Response getAllStateTransitions(@PathParam("messageId") String messageId,
@DefaultValue("default") @QueryParam("language") String language,
@DefaultValue("default") @QueryParam("state") String state) {

return Response.ok()
.entity(messageConstrutor.constructMessage(messageId,language,state))
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.appform.statesman.model.exception.ResponseCode;
import io.appform.statesman.model.exception.StatesmanError;

import javax.annotation.Nullable;
import java.io.IOException;

public class MapperUtils {
private static ObjectMapper objectMapper;
Expand All @@ -33,6 +35,7 @@ public static <T> T deserialize( byte[] data, TypeReference<T> typeReference) {
return deserialize(objectMapper, data, typeReference);
}


@Nullable
public static <T> T deserialize(ObjectMapper mapper, byte[] data, Class<T> valueType) {
try {
Expand Down Expand Up @@ -109,4 +112,19 @@ public static byte[] serialize(ObjectMapper mapper, Object data) {

}
}

@Nullable
public static JsonNode readTree(byte[] data) {
try {
if (data == null) {
return null;
}
return objectMapper.readTree(data);
} catch (JsonProcessingException e) {
throw StatesmanError.propagate(e, ResponseCode.JSON_ERROR);

} catch (IOException e) {
throw StatesmanError.propagate(e, ResponseCode.JSON_ERROR);
}
}
}
Loading