Skip to content

Commit d420371

Browse files
feat: add support for custom objectId (#1088)
1 parent bd3ac1d commit d420371

File tree

6 files changed

+210
-8
lines changed

6 files changed

+210
-8
lines changed

parse/src/main/java/com/parse/Parse.java

+29
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public class Parse {
4949
private static final Object MUTEX_CALLBACKS = new Object();
5050
static ParseEventuallyQueue eventuallyQueue = null;
5151
private static boolean isLocalDatastoreEnabled;
52+
private static boolean allowCustomObjectId = false;
5253

5354
// endregion
5455
private static OfflineStore offlineStore;
@@ -110,6 +111,14 @@ public static boolean isLocalDatastoreEnabled() {
110111
return isLocalDatastoreEnabled;
111112
}
112113

114+
/**
115+
* @return {@code True} if {@link Configuration.Builder#allowCustomObjectId()} has been called,
116+
* otherwise {@code false}.
117+
*/
118+
public static boolean isAllowCustomObjectId() {
119+
return allowCustomObjectId;
120+
}
121+
113122
/**
114123
* Authenticates this client as belonging to your application. This must be called before your
115124
* application can use the Parse library. The recommended way is to put a call to {@code
@@ -140,6 +149,8 @@ static void initialize(Configuration configuration, ParsePlugins parsePlugins) {
140149
// isLocalDataStoreEnabled() to perform additional behavior.
141150
isLocalDatastoreEnabled = configuration.localDataStoreEnabled;
142151

152+
allowCustomObjectId = configuration.allowCustomObjectId;
153+
143154
if (parsePlugins == null) {
144155
ParsePlugins.initialize(configuration.context, configuration);
145156
} else {
@@ -271,6 +282,7 @@ public static void destroy() {
271282
ParsePlugins.reset();
272283

273284
setLocalDatastore(null);
285+
allowCustomObjectId = false;
274286
}
275287

276288
/** @return {@code True} if {@link #initialize} has been called, otherwise {@code false}. */
@@ -573,6 +585,7 @@ public static final class Configuration {
573585
final String clientKey;
574586
final String server;
575587
final boolean localDataStoreEnabled;
588+
final boolean allowCustomObjectId;
576589
final OkHttpClient.Builder clientBuilder;
577590
final int maxRetries;
578591

@@ -582,6 +595,7 @@ private Configuration(Builder builder) {
582595
this.clientKey = builder.clientKey;
583596
this.server = builder.server;
584597
this.localDataStoreEnabled = builder.localDataStoreEnabled;
598+
this.allowCustomObjectId = builder.allowCustomObjectId;
585599
this.clientBuilder = builder.clientBuilder;
586600
this.maxRetries = builder.maxRetries;
587601
}
@@ -593,6 +607,7 @@ public static final class Builder {
593607
private String clientKey;
594608
private String server;
595609
private boolean localDataStoreEnabled;
610+
private boolean allowCustomObjectId;
596611
private OkHttpClient.Builder clientBuilder;
597612
private int maxRetries = DEFAULT_MAX_RETRIES;
598613

@@ -657,6 +672,20 @@ private Builder setLocalDatastoreEnabled(boolean enabled) {
657672
return this;
658673
}
659674

675+
/**
676+
* Allow to set a custom objectId for ParseObjects.
677+
*
678+
* @return The same builder, for easy chaining.
679+
*/
680+
public Builder allowCustomObjectId() {
681+
return this.setAllowCustomObjectId(true);
682+
}
683+
684+
private Builder setAllowCustomObjectId(boolean enabled) {
685+
allowCustomObjectId = enabled;
686+
return this;
687+
}
688+
660689
/**
661690
* Set the {@link okhttp3.OkHttpClient.Builder} to use when communicating with the Parse
662691
* REST API

parse/src/main/java/com/parse/ParseObject.java

+8
Original file line numberDiff line numberDiff line change
@@ -2250,6 +2250,10 @@ Task<Void> saveAsync(final String sessionToken, final Task<Void> toAwait) {
22502250
return Task.forResult(null);
22512251
}
22522252

2253+
if (Parse.isAllowCustomObjectId() && getObjectId() == null) {
2254+
return Task.forError(new ParseException(104, "ObjectId must not be null"));
2255+
}
2256+
22532257
final ParseOperationSet operations;
22542258
synchronized (mutex) {
22552259
updateBeforeSave();
@@ -2357,6 +2361,10 @@ public final Task<Void> saveEventually() {
23572361
return Task.forResult(null);
23582362
}
23592363

2364+
if (Parse.isAllowCustomObjectId() && getObjectId() == null) {
2365+
return Task.forError(new ParseException(104, "ObjectId must not be null"));
2366+
}
2367+
23602368
final ParseOperationSet operationSet;
23612369
final ParseRESTCommand command;
23622370
final Task<JSONObject> runEventuallyTask;

parse/src/main/java/com/parse/ParseRESTObjectCommand.java

+12-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,18 @@ public static ParseRESTObjectCommand saveObjectCommand(
3636
return ParseRESTObjectCommand.createObjectCommand(
3737
state.className(), operations, sessionToken);
3838
} else {
39-
return ParseRESTObjectCommand.updateObjectCommand(
40-
state.objectId(), state.className(), operations, sessionToken);
39+
if (Parse.isAllowCustomObjectId()) {
40+
if (state.createdAt() == -1) {
41+
return ParseRESTObjectCommand.createObjectCommand(
42+
state.className(), operations, sessionToken);
43+
} else {
44+
return ParseRESTObjectCommand.updateObjectCommand(
45+
state.objectId(), state.className(), operations, sessionToken);
46+
}
47+
} else {
48+
return ParseRESTObjectCommand.updateObjectCommand(
49+
state.objectId(), state.className(), operations, sessionToken);
50+
}
4151
}
4252
}
4353

parse/src/test/java/com/parse/ParseClientConfigurationTest.java

+2
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ public void testBuilder() {
2525
builder.applicationId("foo");
2626
builder.clientKey("bar");
2727
builder.enableLocalDataStore();
28+
builder.allowCustomObjectId();
2829
Parse.Configuration configuration = builder.build();
2930

3031
assertNull(configuration.context);
3132
assertEquals(configuration.applicationId, "foo");
3233
assertEquals(configuration.clientKey, "bar");
3334
assertTrue(configuration.localDataStoreEnabled);
35+
assertEquals(configuration.allowCustomObjectId, true);
3436
}
3537

3638
@Test

parse/src/test/java/com/parse/ParseObjectTest.java

+121-5
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88
*/
99
package com.parse;
1010

11+
import static org.hamcrest.core.Is.is;
1112
import static org.junit.Assert.assertEquals;
1213
import static org.junit.Assert.assertFalse;
14+
import static org.junit.Assert.assertNotNull;
1315
import static org.junit.Assert.assertNull;
1416
import static org.junit.Assert.assertSame;
17+
import static org.junit.Assert.assertThat;
1518
import static org.junit.Assert.assertTrue;
1619
import static org.mockito.ArgumentMatchers.nullable;
1720
import static org.mockito.Matchers.any;
@@ -46,7 +49,7 @@
4649
import org.robolectric.RuntimeEnvironment;
4750

4851
@RunWith(RobolectricTestRunner.class)
49-
public class ParseObjectTest {
52+
public class ParseObjectTest extends ResetPluginsParseTest {
5053

5154
@Rule public final ExpectedException thrown = ExpectedException.none();
5255

@@ -80,16 +83,17 @@ private static TaskCompletionSource<Void> mockObjectControllerForDelete() {
8083
}
8184

8285
@Before
83-
public void setUp() {
86+
public void setUp() throws Exception {
87+
super.setUp();
8488
ParseFieldOperations.registerDefaultDecoders(); // to test JSON / Parcel decoding
8589
}
8690

8791
// region testRevert
8892

8993
@After
90-
public void tearDown() {
91-
ParseCorePlugins.getInstance().reset();
92-
ParsePlugins.reset();
94+
public void tearDown() throws Exception {
95+
super.tearDown();
96+
Parse.destroy();
9397
}
9498

9599
@Test
@@ -159,6 +163,118 @@ public void testFromJsonWithLdsStackOverflow() throws JSONException {
159163

160164
// endregion
161165

166+
@Test
167+
public void testSaveCustomObjectIdMissing() {
168+
// Mocked to let save work
169+
mockCurrentUserController();
170+
171+
Parse.Configuration configuration =
172+
new Parse.Configuration.Builder(RuntimeEnvironment.application)
173+
.applicationId(BuildConfig.LIBRARY_PACKAGE_NAME)
174+
.server("https://api.parse.com/1")
175+
.enableLocalDataStore()
176+
.allowCustomObjectId()
177+
.build();
178+
ParsePlugins plugins = mock(ParsePlugins.class);
179+
when(plugins.configuration()).thenReturn(configuration);
180+
when(plugins.applicationContext()).thenReturn(RuntimeEnvironment.application);
181+
Parse.initialize(configuration, plugins);
182+
183+
ParseObject object = new ParseObject("TestObject");
184+
try {
185+
object.save();
186+
} catch (ParseException e) {
187+
assertEquals(e.getCode(), 104);
188+
assertThat(e.getMessage(), is("ObjectId must not be null"));
189+
}
190+
}
191+
192+
@Test
193+
public void testSaveCustomObjectIdNotMissing() {
194+
// Mocked to let save work
195+
mockCurrentUserController();
196+
197+
Parse.Configuration configuration =
198+
new Parse.Configuration.Builder(RuntimeEnvironment.application)
199+
.applicationId(BuildConfig.LIBRARY_PACKAGE_NAME)
200+
.server("https://api.parse.com/1")
201+
.enableLocalDataStore()
202+
.allowCustomObjectId()
203+
.build();
204+
ParsePlugins plugins = mock(ParsePlugins.class);
205+
when(plugins.configuration()).thenReturn(configuration);
206+
when(plugins.applicationContext()).thenReturn(RuntimeEnvironment.application);
207+
Parse.initialize(configuration, plugins);
208+
209+
ParseObject object = new ParseObject("TestObject");
210+
object.setObjectId("ABCDEF123456");
211+
212+
ParseException exception = null;
213+
try {
214+
object.save();
215+
} catch (ParseException e) {
216+
exception = e;
217+
}
218+
assertNull(exception);
219+
}
220+
221+
@Test
222+
public void testSaveEventuallyCustomObjectIdMissing() {
223+
// Mocked to let save work
224+
mockCurrentUserController();
225+
226+
Parse.Configuration configuration =
227+
new Parse.Configuration.Builder(RuntimeEnvironment.application)
228+
.applicationId(BuildConfig.LIBRARY_PACKAGE_NAME)
229+
.server("https://api.parse.com/1")
230+
.enableLocalDataStore()
231+
.allowCustomObjectId()
232+
.build();
233+
ParsePlugins plugins = ParseTestUtils.mockParsePlugins(configuration);
234+
Parse.initialize(configuration, plugins);
235+
236+
ParseObject object = new ParseObject("TestObject");
237+
object.saveEventually(
238+
new SaveCallback() {
239+
@Override
240+
public void done(ParseException e) {
241+
assertNotNull(e);
242+
assertEquals(e.getCode(), 104);
243+
assertThat(e.getMessage(), is("ObjectId must not be null"));
244+
}
245+
});
246+
247+
Parse.setLocalDatastore(null);
248+
}
249+
250+
@Test
251+
public void testSaveEventuallyCustomObjectIdNotMissing() throws ParseException {
252+
// Mocked to let save work
253+
mockCurrentUserController();
254+
255+
Parse.Configuration configuration =
256+
new Parse.Configuration.Builder(RuntimeEnvironment.application)
257+
.applicationId(BuildConfig.LIBRARY_PACKAGE_NAME)
258+
.server("https://api.parse.com/1")
259+
.enableLocalDataStore()
260+
.allowCustomObjectId()
261+
.build();
262+
ParsePlugins plugins = ParseTestUtils.mockParsePlugins(configuration);
263+
Parse.initialize(configuration, plugins);
264+
265+
ParseObject object = new ParseObject("TestObject");
266+
object.setObjectId("ABCDEF123456");
267+
object.saveEventually(
268+
new SaveCallback() {
269+
@Override
270+
public void done(ParseException e) {
271+
assertNull(e);
272+
}
273+
});
274+
275+
Parse.setLocalDatastore(null);
276+
}
277+
162278
// region testGetter
163279

164280
@Test

parse/src/test/java/com/parse/ParseRESTCommandTest.java

+38-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.io.IOException;
3030
import java.io.InputStream;
3131
import java.net.URL;
32+
import java.util.Collections;
3233
import org.json.JSONArray;
3334
import org.json.JSONObject;
3435
import org.junit.After;
@@ -38,6 +39,7 @@
3839
import org.junit.rules.ExpectedException;
3940
import org.junit.runner.RunWith;
4041
import org.robolectric.RobolectricTestRunner;
42+
import org.robolectric.RuntimeEnvironment;
4143
import org.skyscreamer.jsonassert.JSONCompareMode;
4244

4345
// For org.json
@@ -488,7 +490,7 @@ public void testOnResponseCloseNetworkStreamWithNormalResponse() throws Exceptio
488490
}
489491

490492
@Test
491-
public void testOnResposneCloseNetworkStreamWithIOException() throws Exception {
493+
public void testOnResponseCloseNetworkStreamWithIOException() throws Exception {
492494
// Mock response stream
493495
int statusCode = 200;
494496
InputStream mockResponseStream = mock(InputStream.class);
@@ -515,4 +517,39 @@ public void testOnResposneCloseNetworkStreamWithIOException() throws Exception {
515517
assertEquals("Error", responseTask.getError().getMessage());
516518
verify(mockResponseStream, times(1)).close();
517519
}
520+
521+
@Test
522+
public void testSaveObjectCommandUpdate() {
523+
ParseObject.State state = mock(ParseObject.State.class);
524+
when(state.className()).thenReturn("TestObject");
525+
when(state.objectId()).thenReturn("test_id");
526+
when(state.createdAt()).thenReturn(System.currentTimeMillis() / 1000L);
527+
when(state.updatedAt()).thenReturn(System.currentTimeMillis() / 1000L);
528+
when(state.keySet()).thenReturn(Collections.singleton("foo"));
529+
when(state.get("foo")).thenReturn("bar");
530+
ParseObject parseObject = ParseObject.from(state);
531+
532+
ParseRESTObjectCommand command =
533+
ParseRESTObjectCommand.saveObjectCommand(parseObject.getState(), null, null);
534+
assertEquals(command.method, ParseHttpRequest.Method.PUT);
535+
536+
Parse.Configuration configuration =
537+
new Parse.Configuration.Builder(RuntimeEnvironment.application)
538+
.applicationId(BuildConfig.LIBRARY_PACKAGE_NAME)
539+
.server("https://api.parse.com/1")
540+
.enableLocalDataStore()
541+
.allowCustomObjectId()
542+
.build();
543+
ParsePlugins plugins = mock(ParsePlugins.class);
544+
when(plugins.configuration()).thenReturn(configuration);
545+
when(plugins.applicationContext()).thenReturn(RuntimeEnvironment.application);
546+
Parse.initialize(configuration, plugins);
547+
548+
command = ParseRESTObjectCommand.saveObjectCommand(parseObject.getState(), null, null);
549+
assertEquals(command.method, ParseHttpRequest.Method.PUT);
550+
551+
ParseCorePlugins.getInstance().reset();
552+
ParsePlugins.reset();
553+
Parse.destroy();
554+
}
518555
}

0 commit comments

Comments
 (0)