Skip to content

Commit 358268a

Browse files
committed
Adds more, safe point
1 parent 8f7a2a4 commit 358268a

File tree

3 files changed

+187
-52
lines changed

3 files changed

+187
-52
lines changed

src/main/java/io/rejson/Client.java

Lines changed: 87 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ Jedis _conn() {
2323
}
2424

2525
private enum Command implements ProtocolCommand {
26-
2726
DEL("JSON.DEL"),
2827
GET("JSON.GET"),
2928
SET("JSON.SET"),
@@ -39,14 +38,60 @@ public byte[] getRaw() {
3938
}
4039
}
4140

42-
private void assertReplyNotError(final String str) throws Exception {
41+
/**
42+
* Existential modifier for the set command, by default we don't care
43+
*/
44+
public enum ExistenceModifier implements ProtocolCommand {
45+
DEFAULT(""),
46+
NOT_EXISTS("NX"),
47+
MUST_EXIST("XX");
48+
private final byte[] raw;
49+
50+
ExistenceModifier(String alt) {
51+
raw = SafeEncoder.encode(alt);
52+
}
53+
54+
public byte[] getRaw() {
55+
return raw;
56+
}
57+
}
58+
59+
/**
60+
* Helper to check for errors and throw them as an exception
61+
* @param str the reply string to "analyze"
62+
* @throws Exception
63+
*/
64+
private void assertReplyNotError(final String str) {
4365
if (str.startsWith("-ERR"))
44-
throw new Exception(str.substring(5));
66+
throw new RuntimeException(str.substring(5));
4567
}
4668

47-
private void assertReplyOK(final String str) throws Exception {
69+
/**
70+
* Helper to check for an OK reply
71+
* @param str the reply string to "scrutinize"
72+
*/
73+
private void assertReplyOK(final String str) {
4874
if (!str.equals("OK"))
49-
throw new Exception(str);
75+
throw new RuntimeException(str);
76+
}
77+
78+
/**
79+
* Helper to handle single optional path argument situations
80+
* @param path a single optional path
81+
* @return the provided path or root if not
82+
*/
83+
private Path getSingleOptionalPath(Path... path) {
84+
// check for 0, 1 or more paths
85+
if (1 > path.length) {
86+
// default to root
87+
return Path.RootPath();
88+
} else if (1 == path.length){
89+
// take 1
90+
return path[0];
91+
} else {
92+
// throw out the baby with the water
93+
throw new RuntimeException("Only a single optional path is allowed");
94+
}
5095
}
5196

5297
/**
@@ -56,7 +101,7 @@ private void assertReplyOK(final String str) throws Exception {
56101
* @param timeout the timeout
57102
* @param poolSize the pool's size
58103
*/
59-
public Client(final String host, final int port, final int timeout, final int poolSize) {
104+
public Client(String host, int port, int timeout, int poolSize) {
60105
JedisPoolConfig conf = new JedisPoolConfig();
61106
conf.setMaxTotal(poolSize);
62107
conf.setTestOnBorrow(false);
@@ -73,26 +118,31 @@ public Client(final String host, final int port, final int timeout, final int po
73118

74119
}
75120

76-
public Client(final String host, final int port) {
121+
/**
122+
* Create a new client with default timeout and poolSize
123+
* @param host the Redis host
124+
* @param port the Redis port
125+
*/
126+
public Client(String host, int port) {
77127
this(host, port, 500, 100);
78128
}
79129

80130
/**
81131
* Deletes a path
82132
* @param key the key name
83-
* @param path a path in the object
133+
* @param path optional single path in the object, defaults to root
84134
* @return the number of paths deleted (0 or 1)
85135
*/
86-
public Long del(final String key, final Path path) throws Exception {
136+
public Long del(String key, Path... path) {
87137
Jedis conn = _conn();
88-
ArrayList<byte[]> args = new ArrayList(3);
138+
ArrayList<byte[]> args = new ArrayList(2);
89139

90140
args.add(SafeEncoder.encode(key));
91-
args.add(SafeEncoder.encode(path.toString()));
141+
args.add(SafeEncoder.encode(getSingleOptionalPath(path).toString()));
92142

93143
Long rep = conn.getClient()
94-
.sendCommand(Command.DEL, args.toArray(new byte[args.size()][]))
95-
.getIntegerReply();
144+
.sendCommand(Command.DEL, args.toArray(new byte[args.size()][]))
145+
.getIntegerReply();
96146
conn.close();
97147

98148
return rep;
@@ -101,10 +151,10 @@ public Long del(final String key, final Path path) throws Exception {
101151
/**
102152
* Gets an object
103153
* @param key the key name
104-
* @param paths a path in the object
154+
* @param paths optional one ore more paths in the object, defaults to root
105155
* @return the requested object
106156
*/
107-
public Object get(final String key, final Path... paths) throws Exception {
157+
public Object get(String key, Path... paths) {
108158
Jedis conn = _conn();
109159
ArrayList<byte[]> args = new ArrayList(2);
110160

@@ -127,17 +177,20 @@ public Object get(final String key, final Path... paths) throws Exception {
127177
/**
128178
* Sets an object
129179
* @param key the key name
130-
* @param path a path in the object
131180
* @param object the Java object to store
181+
* @param flag an existential modifier
182+
* @param path optional single path in the object, defaults to root
132183
*/
133-
public void set(final String key, final Path path, final Object object) throws Exception {
134-
// TODO: support NX|XX flags
184+
public void set(String key, Object object, ExistenceModifier flag, Path... path) {
135185
Jedis conn = _conn();
136-
ArrayList<byte[]> args = new ArrayList(3);
186+
ArrayList<byte[]> args = new ArrayList(4);
137187

138188
args.add(SafeEncoder.encode(key));
139-
args.add(SafeEncoder.encode(path.toString()));
189+
args.add(SafeEncoder.encode(getSingleOptionalPath(path).toString()));
140190
args.add(SafeEncoder.encode(gson.toJson(object)));
191+
if (ExistenceModifier.DEFAULT != flag) {
192+
args.add(flag.getRaw());
193+
}
141194

142195
String status = conn.getClient()
143196
.sendCommand(Command.SET, args.toArray(new byte[args.size()][]))
@@ -147,18 +200,28 @@ public void set(final String key, final Path path, final Object object) throws E
147200
assertReplyOK(status);
148201
}
149202

203+
/**
204+
* Sets an object without caring about target path existing
205+
* @param key the key name
206+
* @param object the Java object to store
207+
* @param path optional single path in the object, defaults to root
208+
*/
209+
public void set(String key, Object object, Path... path) {
210+
this.set(key, object, ExistenceModifier.DEFAULT, path);
211+
}
212+
150213
/**
151214
* Gets the class of an object
152215
* @param key the key name
153-
* @param path a path in the object
216+
* @param path optional single path in the object, defaults to root
154217
* @return the Java class of the requested object
155218
*/
156-
public Class<? extends Object> type(final String key, final Path path) throws Exception {
219+
public Class<? extends Object> type(String key, Path... path) {
157220
Jedis conn = _conn();
158221
ArrayList<byte[]> args = new ArrayList(2);
159222

160223
args.add(SafeEncoder.encode(key));
161-
args.add(SafeEncoder.encode(path.toString()));
224+
args.add(SafeEncoder.encode(getSingleOptionalPath(path).toString()));
162225

163226
String rep = conn.getClient()
164227
.sendCommand(Command.TYPE, args.toArray(new byte[args.size()][]))
@@ -183,8 +246,7 @@ public Class<? extends Object> type(final String key, final Path path) throws Ex
183246
case "array":
184247
return List.class;
185248
default:
186-
throw new Exception(rep);
249+
throw new java.lang.RuntimeException(rep);
187250
}
188251
}
189-
190252
}

src/main/java/io/rejson/Path.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
package io.rejson;
22

3+
/**
4+
* Path is a ReJSON path, representing a valid path into an object
5+
* TODO: make path building even more fun
6+
*/
7+
38
public class Path {
49
private final String strPath;
510

611
public Path(final String strPath) {
712
this.strPath = strPath;
813
}
914

15+
/**
16+
* Makes a root path
17+
* @return the root path
18+
*/
1019
public static Path RootPath() {
1120
return new Path(".");
1221
}

src/test/java/io/rejson/ClientTest.java

Lines changed: 91 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -38,76 +38,140 @@ public void initialize() {
3838
}
3939

4040
@Test
41-
public void set() throws Exception {
41+
public void basicSetGetShouldSucceed() throws Exception {
4242
c._conn().flushDB();
4343

44-
c.set("null", Path.RootPath(), null);
45-
c.set("foobar", Path.RootPath(), new FooBarObject());
44+
// naive set with a path
45+
c.set("null", null, Path.RootPath());
46+
assertNull(c.get("null", Path.RootPath()));
47+
48+
// real scalar value and no path
49+
c.set("str", "strong");
50+
assertEquals("strong", c.get("str"));
51+
52+
// A slightly more complex object
53+
IRLObject obj = new IRLObject();
54+
c.set("obj", obj);
55+
Object expected = g.fromJson(g.toJson(obj), Object.class);
56+
assertTrue(expected.equals(c.get("obj")));
57+
58+
// check an update
59+
Path p = new Path(".str");
60+
c.set("obj", "strung", p);
61+
assertEquals("strung", c.get("obj", p));
62+
}
63+
64+
@Test
65+
public void setExistingPathOnlyIfExistsShouldSucceed() throws Exception {
66+
c._conn().flushDB();
67+
68+
c.set("obj", new IRLObject());
69+
Path p = new Path(".str");
70+
c.set("obj", "strangle", Client.ExistenceModifier.MUST_EXIST, p);
71+
assertEquals("strangle", c.get("obj", p));
72+
}
73+
74+
@Test
75+
public void setNonExistingOnlyIfNotExistsShouldSucceed() throws Exception {
76+
c._conn().flushDB();
77+
78+
c.set("obj", new IRLObject());
79+
Path p = new Path(".none");
80+
c.set("obj", "strangle", Client.ExistenceModifier.NOT_EXISTS, p);
81+
assertEquals("strangle", c.get("obj", p));
82+
}
83+
84+
@Test(expected = Exception.class)
85+
public void setExistingPathOnlyIfNotExistsShouldFail() throws Exception {
86+
c._conn().flushDB();
87+
88+
c.set("obj", new IRLObject());
89+
Path p = new Path(".str");
90+
c.set("obj", "strangle", Client.ExistenceModifier.NOT_EXISTS, p);
91+
}
92+
93+
@Test(expected = Exception.class)
94+
public void setNonExistingPathOnlyIfExistsShouldFail() throws Exception {
95+
c._conn().flushDB();
96+
97+
c.set("obj", new IRLObject());
98+
Path p = new Path(".none");
99+
c.set("obj", "strangle", Client.ExistenceModifier.MUST_EXIST, p);
46100
}
47101

48102
@Test(expected = Exception.class)
49103
public void setException() throws Exception {
50104
c._conn().flushDB();
51105

52106
// should error on non root path for new key
53-
c.set("test", new Path(".foo"), "bar");
107+
c.set("test", "bar", new Path(".foo"));
54108
}
55109

56-
@Test
57-
public void get() throws Exception {
110+
@Test(expected = Exception.class)
111+
public void setMultiplePathsShouldFail() throws Exception {
58112
c._conn().flushDB();
113+
c.set("obj", new IRLObject());
114+
c.set("obj", "strange", new Path(".str"), new Path(".str"));
115+
}
59116

60-
// check naive path
61-
c.set("str", Path.RootPath(), "foo");
62-
assertEquals("foo", c.get("str", Path.RootPath()));
117+
@Test
118+
public void getMultiplePathsShouldSucceed() throws Exception {
119+
c._conn().flushDB();
63120

64121
// check multiple paths
65-
IRLObject irlObj = new IRLObject();
66-
c.set("irlobj", Path.RootPath(), irlObj);
67-
Object expected = g.fromJson(g.toJson(irlObj), Object.class);
68-
assertTrue(
69-
expected.equals(
70-
c.get("irlobj", new Path("bTrue"), new Path("str"))));
122+
IRLObject obj = new IRLObject();
123+
c.set("obj", obj);
124+
Object expected = g.fromJson(g.toJson(obj), Object.class);
125+
assertTrue(expected.equals(c.get("obj", new Path("bTrue"), new Path("str"))));
71126

72-
// check default root path
73-
assertTrue(
74-
expected.equals(
75-
c.get("irlobj")));
76127
}
77128

78129
@Test(expected = Exception.class)
79130
public void getException() throws Exception {
80131
c._conn().flushDB();
81-
c.set("test", Path.RootPath(), "foo");
132+
c.set("test", "foo", Path.RootPath());
82133
c.get("test", new Path(".bar"));
83134
}
84135

85136
@Test
86-
public void del() throws Exception {
137+
public void delValidShouldSucceed() throws Exception {
87138
c._conn().flushDB();
88-
c.set("foobar", Path.RootPath(), new FooBarObject());
89-
c.del("foobar", new Path(".foo"));
139+
140+
// check deletion of a single path
141+
c.set("obj", new IRLObject(), Path.RootPath());
142+
c.del("obj", new Path(".str"));
143+
assertTrue(c._conn().exists("obj"));
144+
145+
// check deletion root using default root -> key is removed
146+
c.del("obj");
147+
assertFalse(c._conn().exists("obj"));
90148
}
91149

92150
@Test(expected = Exception.class)
93151
public void delException() throws Exception {
94152
c._conn().flushDB();
95-
c.set("foobar", Path.RootPath(), new FooBarObject());
153+
c.set("foobar", new FooBarObject(), Path.RootPath());
96154
c.del("foobar", new Path(".foo[1]"));
97155
}
98156

157+
@Test(expected = Exception.class)
158+
public void delMultiplePathsShoudFail() throws Exception {
159+
c._conn().flushDB();
160+
c.del("foobar", new Path(".foo"), new Path(".bar"));
161+
}
162+
99163
@Test
100-
public void type() throws Exception {
164+
public void typeChecksShouldSucceed() throws Exception {
101165
c._conn().flushDB();
102-
c.set("foobar", Path.RootPath(), new FooBarObject());
166+
c.set("foobar", new FooBarObject(), Path.RootPath());
103167
assertSame(Object.class, c.type("foobar", Path.RootPath()));
104168
assertSame(String.class, c.type("foobar", new Path(".foo")));
105169
}
106170

107171
@Test(expected = Exception.class)
108172
public void typeException() throws Exception {
109173
c._conn().flushDB();
110-
c.set("foobar", Path.RootPath(), new FooBarObject());
174+
c.set("foobar", new FooBarObject(), Path.RootPath());
111175
c.type("foobar", new Path(".foo[1]"));
112176
}
113177

0 commit comments

Comments
 (0)