Skip to content

Commit fc36536

Browse files
committed
feat(core): implement command execution for json type
1 parent 2b00363 commit fc36536

File tree

6 files changed

+186
-22
lines changed

6 files changed

+186
-22
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.brotherjing.core.executor;
2+
3+
import java.util.List;
4+
5+
import com.brotherjing.core.dto.SnapshotDto;
6+
import com.brotherjing.core.model.exception.CommandException;
7+
import com.brotherjing.proto.BaseProto;
8+
import com.google.protobuf.InvalidProtocolBufferException;
9+
import com.google.protobuf.Message;
10+
11+
public abstract class AbstractCommandExecutor<T extends Message, D> implements ICommandExecutor {
12+
13+
@Override
14+
public void apply(SnapshotDto dto, List<BaseProto.Command> commands) throws CommandException {
15+
D data;
16+
try {
17+
data = deserialize(dto.getData());
18+
} catch (Exception e) {
19+
throw new CommandException(e);
20+
}
21+
for (BaseProto.Command command : commands) {
22+
data = applySingle(data, command);
23+
}
24+
try {
25+
dto.setData(serialize(data));
26+
} catch (Exception e) {
27+
throw new CommandException(e);
28+
}
29+
}
30+
31+
private D applySingle(D data, BaseProto.Command command) {
32+
try {
33+
if (command.getOp().is(getOpClass())) {
34+
T op = command.getOp().unpack(getOpClass());
35+
return applyOp(data, op);
36+
}
37+
} catch (InvalidProtocolBufferException e) {
38+
e.printStackTrace();
39+
}
40+
return data;
41+
}
42+
43+
protected abstract D deserialize(String data) throws Exception;
44+
45+
protected abstract String serialize(D data) throws Exception;
46+
47+
protected abstract Class<T> getOpClass();
48+
49+
protected abstract D applyOp(D data, T op);
50+
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.brotherjing.core.executor;
22

3+
import java.util.List;
4+
35
import com.brotherjing.core.dto.SnapshotDto;
6+
import com.brotherjing.core.model.exception.CommandException;
47
import com.brotherjing.proto.BaseProto;
58

69
public interface ICommandExecutor {
710

811
BaseProto.DocType getSupportedType();
912

10-
void applySingle(SnapshotDto dto, BaseProto.Command command);
11-
13+
void apply(SnapshotDto dto, List<BaseProto.Command> commands) throws CommandException;
1214
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package com.brotherjing.core.executor.impl;
2+
3+
import java.io.IOException;
4+
import java.util.List;
5+
6+
import lombok.extern.slf4j.Slf4j;
7+
8+
import org.springframework.stereotype.Component;
9+
10+
import com.brotherjing.core.executor.AbstractCommandExecutor;
11+
import com.brotherjing.proto.BaseProto;
12+
import com.brotherjing.proto.JsonProto;
13+
import com.fasterxml.jackson.core.JsonProcessingException;
14+
import com.fasterxml.jackson.databind.JsonNode;
15+
import com.fasterxml.jackson.databind.ObjectMapper;
16+
import com.fasterxml.jackson.databind.node.ArrayNode;
17+
import com.fasterxml.jackson.databind.node.IntNode;
18+
import com.fasterxml.jackson.databind.node.ObjectNode;
19+
import com.fasterxml.jackson.databind.node.TextNode;
20+
21+
@Slf4j
22+
@Component
23+
public class JsonCommandExecutor extends AbstractCommandExecutor<JsonProto.Operations, JsonNode> {
24+
25+
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
26+
27+
@Override
28+
public BaseProto.DocType getSupportedType() {
29+
return BaseProto.DocType.JSON;
30+
}
31+
32+
@Override
33+
protected Class<JsonProto.Operations> getOpClass() {
34+
return JsonProto.Operations.class;
35+
}
36+
37+
@Override
38+
protected JsonNode deserialize(String data) throws IOException {
39+
return OBJECT_MAPPER.readTree(data);
40+
}
41+
42+
@Override
43+
protected String serialize(JsonNode data) throws JsonProcessingException {
44+
return OBJECT_MAPPER.writeValueAsString(data);
45+
}
46+
47+
@Override
48+
protected JsonNode applyOp(JsonNode data, JsonProto.Operations op) {
49+
JsonNode pointer = data;
50+
for (JsonProto.Operation operation : op.getOpsList()) {
51+
List<JsonProto.Path> pathList = operation.getPathList();
52+
for (JsonProto.Path path : pathList.subList(0, pathList.size() - 1)) {
53+
if (path.getTypeCase().equals(JsonProto.Path.TypeCase.INDEX)) {
54+
pointer = pointer.get(path.getIndex());
55+
} else {
56+
pointer = pointer.get(path.getKey());
57+
}
58+
}
59+
// operate on the leaf node
60+
JsonProto.Path lastPath = pathList.get(pathList.size() - 1);
61+
if (lastPath.getTypeCase().equals(JsonProto.Path.TypeCase.INDEX)) {
62+
ArrayNode arrayNode = (ArrayNode)pointer;
63+
if (operation.hasLi() && operation.hasLd()) {
64+
// replace
65+
arrayNode.set(lastPath.getIndex(), toJsonNode(operation.getLi()));
66+
} else if (operation.hasLi()) {
67+
// insert
68+
arrayNode.insert(lastPath.getIndex(), toJsonNode(operation.getLi()));
69+
} else if (operation.hasLd()) {
70+
// remove
71+
arrayNode.remove(lastPath.getIndex());
72+
}
73+
} else if (lastPath.getTypeCase().equals(JsonProto.Path.TypeCase.KEY)) {
74+
ObjectNode objectNode = (ObjectNode)pointer;
75+
if (operation.hasOi()) {
76+
objectNode.set(lastPath.getKey(), toJsonNode(operation.getOi()));
77+
} else if (operation.hasOd()) {
78+
objectNode.remove(lastPath.getKey());
79+
}
80+
}
81+
}
82+
return data;
83+
}
84+
85+
private JsonNode toJsonNode(JsonProto.Payload payload) {
86+
switch (payload.getTypeCase()) {
87+
case JSON:
88+
try {
89+
return OBJECT_MAPPER.readTree(payload.getJson());
90+
} catch (IOException e) {
91+
e.printStackTrace();
92+
return null;
93+
}
94+
case TEXT:
95+
return new TextNode(payload.getText());
96+
case NUMBER:
97+
return new IntNode(payload.getNumber());
98+
}
99+
return null;
100+
}
101+
}

scalable-ot-core/src/main/java/com/brotherjing/core/executor/impl/TextCommandExecutor.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,42 @@
77

88
import org.springframework.stereotype.Component;
99

10-
import com.brotherjing.core.dto.SnapshotDto;
11-
import com.brotherjing.core.executor.ICommandExecutor;
10+
import com.brotherjing.core.executor.AbstractCommandExecutor;
1211
import com.brotherjing.proto.BaseProto;
1312
import com.brotherjing.proto.TextProto;
14-
import com.google.protobuf.InvalidProtocolBufferException;
1513

1614
@Slf4j
1715
@Component
18-
public class TextCommandExecutor implements ICommandExecutor {
16+
public class TextCommandExecutor extends AbstractCommandExecutor<TextProto.Operation, String> {
1917

2018
@Override
2119
public BaseProto.DocType getSupportedType() {
2220
return BaseProto.DocType.PLAIN_TEXT;
2321
}
2422

2523
@Override
26-
public void applySingle(SnapshotDto dto, BaseProto.Command command) {
27-
log.info("command type is {}", command.getOp().getTypeUrl());
28-
try {
29-
if (command.getOp().is(TextProto.Operation.class)) {
30-
TextProto.Operation op = command.getOp().unpack(TextProto.Operation.class);
31-
applyTextOp(dto, op);
32-
}
33-
} catch (InvalidProtocolBufferException e) {
34-
e.printStackTrace();
35-
}
24+
protected Class<TextProto.Operation> getOpClass() {
25+
return TextProto.Operation.class;
26+
}
27+
28+
@Override
29+
protected String deserialize(String data) {
30+
return data;
3631
}
3732

38-
private void applyTextOp(SnapshotDto dto, TextProto.Operation op) {
33+
@Override
34+
protected String serialize(String data) {
35+
return data;
36+
}
37+
38+
@Override
39+
protected String applyOp(String data, TextProto.Operation op) {
3940
List<TextProto.Operation> operations;
4041
if (op.hasMultiple()) {
4142
operations = op.getMultiple().getOpsList();
4243
} else {
4344
operations = Collections.singletonList(op);
4445
}
45-
String data = dto.getData();
4646
int index = 0;
4747
for (TextProto.Operation operation : operations) {
4848
switch (operation.getType()) {
@@ -63,6 +63,6 @@ private void applyTextOp(SnapshotDto dto, TextProto.Operation op) {
6363
break;
6464
}
6565
}
66-
dto.setData(data);
66+
return data;
6767
}
6868
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.brotherjing.core.model.exception;
2+
3+
public class CommandException extends Exception {
4+
public CommandException(Throwable cause) {
5+
super(cause);
6+
}
7+
}

scalable-ot-core/src/main/java/com/brotherjing/core/service/impl/DocServiceImpl.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.brotherjing.core.dto.SnapshotDto;
1919
import com.brotherjing.core.executor.CommandExecutorRegistry;
2020
import com.brotherjing.core.executor.ICommandExecutor;
21+
import com.brotherjing.core.model.exception.CommandException;
2122
import com.brotherjing.core.service.DocService;
2223
import com.brotherjing.core.util.Converter;
2324
import com.brotherjing.core.util.IDUtils;
@@ -145,9 +146,12 @@ private void apply(SnapshotDto dto, List<BaseProto.Command> commands) {
145146
log.error("Cannot find executor for this command type: {}", commands.get(0).getType().name());
146147
return;
147148
}
148-
for (BaseProto.Command command : commands) {
149-
executor.applySingle(dto, command);
150-
dto.setVersion(dto.getVersion() + 1);
149+
int version = dto.getVersion();
150+
try {
151+
executor.apply(dto, commands);
152+
dto.setVersion(version + commands.size());
153+
} catch (CommandException e) {
154+
e.printStackTrace();
151155
}
152156
}
153157

0 commit comments

Comments
 (0)