Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for custom objectId #1088

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
419bba1
Add option allowCustomObjectId
Apr 9, 2021
1866c16
Use POST request in case objectId is set and allowCustomObjectId is true
Apr 9, 2021
444fca0
Exclude ParseInstallation when saving
Apr 9, 2021
aa08665
Check for objectId when allowCustomObjectId is true
Apr 9, 2021
7d86008
Check for new object for selecting POST request
Apr 9, 2021
c72fbbb
Use correct error code
Apr 11, 2021
5688270
Same logic as in JS SDK for deciding about HTTP request
Apr 11, 2021
c5f1eee
Fix comparison
Apr 11, 2021
b268f38
Add unit tests for custom objectId
Apr 12, 2021
2948b8c
Format code and add missing assertion to test
Oct 11, 2021
f308e10
Merge branch 'master' of https://github.com/parse-community/Parse-SDK…
Oct 11, 2021
bad7297
Fixes corresponding to changes in version 2.0.0
Oct 11, 2021
0d54b49
chore(release): 2.0.1 [skip ci]
semantic-release-bot Oct 14, 2021
a091eb1
docs: remove 2.0.1 release entry
mtrezza Oct 14, 2021
64bcb3b
chore(release): 2.0.1 [skip ci]
semantic-release-bot Oct 14, 2021
f8bca7c
Merge branch 'master' of https://github.com/parse-community/Parse-SDK…
Oct 14, 2021
f3b227a
ci: temporarily disable release automation
mtrezza Oct 14, 2021
49a76be
Merge branch 'master' of https://github.com/parse-community/Parse-SDK…
Oct 14, 2021
ca6abb1
Merge branch 'master' of https://github.com/parse-community/Parse-SDK…
Oct 14, 2021
9bffeda
Revert release-automated.yaml
Oct 14, 2021
c3c93c5
Changes of spotlessApply
Oct 15, 2021
f77e1cd
Update unit tests
Oct 15, 2021
08cc3da
Manually reset ParseConfiguration after custom objectId tests
martinpfannemueller Oct 16, 2021
a742f34
Fix broken state of LDS
martinpfannemueller Oct 17, 2021
71ec1c3
Fix failing release build
martinpfannemueller Oct 17, 2021
e44c1de
Remove last Parse.destroy() calls
martinpfannemueller Oct 17, 2021
96c1078
Merge remote-tracking branch 'refs/remotes/origin_original/master'
martinpfannemueller Oct 17, 2021
3f538a0
Merge branch 'master' into feature/custom_objectId
mtrezza Oct 18, 2021
6a56ca3
Merge branch 'master' into feature/custom_objectId
mtrezza Oct 18, 2021
4a2605b
Merge commit '0a47591b852c085b0a08f904ec16ce3806740f14'
martinpfannemueller Oct 24, 2021
858c724
Reuse ResetPluginsParseTest class
martinpfannemueller Oct 24, 2021
84d3eaf
Destroy Parse instance after ParseObjectTests
martinpfannemueller Oct 24, 2021
5bc7420
Properly reset Parse instance on destroy
martinpfannemueller Oct 24, 2021
fa5a0fb
Use setter method for setting allowCustomObjectId
Oct 25, 2021
8e00af8
Add missing ;
Oct 25, 2021
c444e6b
Fix typo in test name
Oct 27, 2021
ae9d016
Add test for saveObjectCommand
Oct 27, 2021
f9f4519
Run spotlessApply
Oct 27, 2021
46531f1
Merge branch 'master' of https://github.com/parse-community/Parse-SDK…
Oct 27, 2021
408acba
Merge branch 'master' into feature/custom_objectId
mtrezza Nov 3, 2021
9e70f7f
Add JavaDoc for allowCustomObjectId method
Nov 21, 2021
bd50d60
Merge branch 'master' of https://github.com/parse-community/Parse-SDK…
Nov 21, 2021
8130153
Add JavaDoc to isAllowCustomObjectId()
Nov 21, 2021
a106246
Update documentation
Nov 21, 2021
461a8db
Run spotlessApply
Nov 21, 2021
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
29 changes: 29 additions & 0 deletions parse/src/main/java/com/parse/Parse.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public class Parse {
private static final Object MUTEX_CALLBACKS = new Object();
static ParseEventuallyQueue eventuallyQueue = null;
private static boolean isLocalDatastoreEnabled;
private static boolean allowCustomObjectId = false;

// endregion
private static OfflineStore offlineStore;
Expand Down Expand Up @@ -110,6 +111,14 @@ public static boolean isLocalDatastoreEnabled() {
return isLocalDatastoreEnabled;
}

/**
* @return {@code True} if {@link Configuration.Builder#allowCustomObjectId()} has been called,
* otherwise {@code false}.
*/
public static boolean isAllowCustomObjectId() {
return allowCustomObjectId;
}

/**
* Authenticates this client as belonging to your application. This must be called before your
* application can use the Parse library. The recommended way is to put a call to {@code
Expand Down Expand Up @@ -140,6 +149,8 @@ static void initialize(Configuration configuration, ParsePlugins parsePlugins) {
// isLocalDataStoreEnabled() to perform additional behavior.
isLocalDatastoreEnabled = configuration.localDataStoreEnabled;

allowCustomObjectId = configuration.allowCustomObjectId;

if (parsePlugins == null) {
ParsePlugins.initialize(configuration.context, configuration);
} else {
Expand Down Expand Up @@ -271,6 +282,7 @@ public static void destroy() {
ParsePlugins.reset();

setLocalDatastore(null);
allowCustomObjectId = false;
}

/** @return {@code True} if {@link #initialize} has been called, otherwise {@code false}. */
Expand Down Expand Up @@ -573,6 +585,7 @@ public static final class Configuration {
final String clientKey;
final String server;
final boolean localDataStoreEnabled;
final boolean allowCustomObjectId;
final OkHttpClient.Builder clientBuilder;
final int maxRetries;

Expand All @@ -582,6 +595,7 @@ private Configuration(Builder builder) {
this.clientKey = builder.clientKey;
this.server = builder.server;
this.localDataStoreEnabled = builder.localDataStoreEnabled;
this.allowCustomObjectId = builder.allowCustomObjectId;
this.clientBuilder = builder.clientBuilder;
this.maxRetries = builder.maxRetries;
}
Expand All @@ -593,6 +607,7 @@ public static final class Builder {
private String clientKey;
private String server;
private boolean localDataStoreEnabled;
private boolean allowCustomObjectId;
private OkHttpClient.Builder clientBuilder;
private int maxRetries = DEFAULT_MAX_RETRIES;

Expand Down Expand Up @@ -657,6 +672,20 @@ private Builder setLocalDatastoreEnabled(boolean enabled) {
return this;
}

/**
* Allow to set a custom objectId for ParseObjects.
*
* @return The same builder, for easy chaining.
*/
public Builder allowCustomObjectId() {
return this.setAllowCustomObjectId(true);
}

private Builder setAllowCustomObjectId(boolean enabled) {
allowCustomObjectId = enabled;
return this;
}

/**
* Set the {@link okhttp3.OkHttpClient.Builder} to use when communicating with the Parse
* REST API
Expand Down
8 changes: 8 additions & 0 deletions parse/src/main/java/com/parse/ParseObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -2250,6 +2250,10 @@ Task<Void> saveAsync(final String sessionToken, final Task<Void> toAwait) {
return Task.forResult(null);
}

if (Parse.isAllowCustomObjectId() && getObjectId() == null) {
return Task.forError(new ParseException(104, "ObjectId must not be null"));
}

final ParseOperationSet operations;
synchronized (mutex) {
updateBeforeSave();
Expand Down Expand Up @@ -2357,6 +2361,10 @@ public final Task<Void> saveEventually() {
return Task.forResult(null);
}

if (Parse.isAllowCustomObjectId() && getObjectId() == null) {
return Task.forError(new ParseException(104, "ObjectId must not be null"));
}

final ParseOperationSet operationSet;
final ParseRESTCommand command;
final Task<JSONObject> runEventuallyTask;
Expand Down
14 changes: 12 additions & 2 deletions parse/src/main/java/com/parse/ParseRESTObjectCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,18 @@ public static ParseRESTObjectCommand saveObjectCommand(
return ParseRESTObjectCommand.createObjectCommand(
state.className(), operations, sessionToken);
} else {
return ParseRESTObjectCommand.updateObjectCommand(
state.objectId(), state.className(), operations, sessionToken);
if (Parse.isAllowCustomObjectId()) {
if (state.createdAt() == -1) {
return ParseRESTObjectCommand.createObjectCommand(
state.className(), operations, sessionToken);
} else {
return ParseRESTObjectCommand.updateObjectCommand(
state.objectId(), state.className(), operations, sessionToken);
}
} else {
return ParseRESTObjectCommand.updateObjectCommand(
state.objectId(), state.className(), operations, sessionToken);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ public void testBuilder() {
builder.applicationId("foo");
builder.clientKey("bar");
builder.enableLocalDataStore();
builder.allowCustomObjectId();
Parse.Configuration configuration = builder.build();

assertNull(configuration.context);
assertEquals(configuration.applicationId, "foo");
assertEquals(configuration.clientKey, "bar");
assertTrue(configuration.localDataStoreEnabled);
assertEquals(configuration.allowCustomObjectId, true);
}

@Test
Expand Down
126 changes: 121 additions & 5 deletions parse/src/test/java/com/parse/ParseObjectTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
*/
package com.parse;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
Expand Down Expand Up @@ -46,7 +49,7 @@
import org.robolectric.RuntimeEnvironment;

@RunWith(RobolectricTestRunner.class)
public class ParseObjectTest {
public class ParseObjectTest extends ResetPluginsParseTest {

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

Expand Down Expand Up @@ -80,16 +83,17 @@ private static TaskCompletionSource<Void> mockObjectControllerForDelete() {
}

@Before
public void setUp() {
public void setUp() throws Exception {
super.setUp();
ParseFieldOperations.registerDefaultDecoders(); // to test JSON / Parcel decoding
}

// region testRevert

@After
public void tearDown() {
ParseCorePlugins.getInstance().reset();
ParsePlugins.reset();
public void tearDown() throws Exception {
super.tearDown();
Parse.destroy();
}

@Test
Expand Down Expand Up @@ -159,6 +163,118 @@ public void testFromJsonWithLdsStackOverflow() throws JSONException {

// endregion

@Test
public void testSaveCustomObjectIdMissing() {
// Mocked to let save work
mockCurrentUserController();

Parse.Configuration configuration =
new Parse.Configuration.Builder(RuntimeEnvironment.application)
.applicationId(BuildConfig.LIBRARY_PACKAGE_NAME)
.server("https://api.parse.com/1")
.enableLocalDataStore()
.allowCustomObjectId()
.build();
ParsePlugins plugins = mock(ParsePlugins.class);
when(plugins.configuration()).thenReturn(configuration);
when(plugins.applicationContext()).thenReturn(RuntimeEnvironment.application);
Parse.initialize(configuration, plugins);

ParseObject object = new ParseObject("TestObject");
try {
object.save();
} catch (ParseException e) {
assertEquals(e.getCode(), 104);
assertThat(e.getMessage(), is("ObjectId must not be null"));
}
}

@Test
public void testSaveCustomObjectIdNotMissing() {
// Mocked to let save work
mockCurrentUserController();

Parse.Configuration configuration =
new Parse.Configuration.Builder(RuntimeEnvironment.application)
.applicationId(BuildConfig.LIBRARY_PACKAGE_NAME)
.server("https://api.parse.com/1")
.enableLocalDataStore()
.allowCustomObjectId()
.build();
ParsePlugins plugins = mock(ParsePlugins.class);
when(plugins.configuration()).thenReturn(configuration);
when(plugins.applicationContext()).thenReturn(RuntimeEnvironment.application);
Parse.initialize(configuration, plugins);

ParseObject object = new ParseObject("TestObject");
object.setObjectId("ABCDEF123456");

ParseException exception = null;
try {
object.save();
} catch (ParseException e) {
exception = e;
}
assertNull(exception);
}

@Test
public void testSaveEventuallyCustomObjectIdMissing() {
// Mocked to let save work
mockCurrentUserController();

Parse.Configuration configuration =
new Parse.Configuration.Builder(RuntimeEnvironment.application)
.applicationId(BuildConfig.LIBRARY_PACKAGE_NAME)
.server("https://api.parse.com/1")
.enableLocalDataStore()
.allowCustomObjectId()
.build();
ParsePlugins plugins = ParseTestUtils.mockParsePlugins(configuration);
Parse.initialize(configuration, plugins);

ParseObject object = new ParseObject("TestObject");
object.saveEventually(
new SaveCallback() {
@Override
public void done(ParseException e) {
assertNotNull(e);
assertEquals(e.getCode(), 104);
assertThat(e.getMessage(), is("ObjectId must not be null"));
}
});

Parse.setLocalDatastore(null);
}

@Test
public void testSaveEventuallyCustomObjectIdNotMissing() throws ParseException {
// Mocked to let save work
mockCurrentUserController();

Parse.Configuration configuration =
new Parse.Configuration.Builder(RuntimeEnvironment.application)
.applicationId(BuildConfig.LIBRARY_PACKAGE_NAME)
.server("https://api.parse.com/1")
.enableLocalDataStore()
.allowCustomObjectId()
.build();
ParsePlugins plugins = ParseTestUtils.mockParsePlugins(configuration);
Parse.initialize(configuration, plugins);

ParseObject object = new ParseObject("TestObject");
object.setObjectId("ABCDEF123456");
object.saveEventually(
new SaveCallback() {
@Override
public void done(ParseException e) {
assertNull(e);
}
});

Parse.setLocalDatastore(null);
}

// region testGetter

@Test
Expand Down
39 changes: 38 additions & 1 deletion parse/src/test/java/com/parse/ParseRESTCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.After;
Expand All @@ -38,6 +39,7 @@
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.skyscreamer.jsonassert.JSONCompareMode;

// For org.json
Expand Down Expand Up @@ -488,7 +490,7 @@ public void testOnResponseCloseNetworkStreamWithNormalResponse() throws Exceptio
}

@Test
public void testOnResposneCloseNetworkStreamWithIOException() throws Exception {
public void testOnResponseCloseNetworkStreamWithIOException() throws Exception {
// Mock response stream
int statusCode = 200;
InputStream mockResponseStream = mock(InputStream.class);
Expand All @@ -515,4 +517,39 @@ public void testOnResposneCloseNetworkStreamWithIOException() throws Exception {
assertEquals("Error", responseTask.getError().getMessage());
verify(mockResponseStream, times(1)).close();
}

@Test
public void testSaveObjectCommandUpdate() {
ParseObject.State state = mock(ParseObject.State.class);
when(state.className()).thenReturn("TestObject");
when(state.objectId()).thenReturn("test_id");
when(state.createdAt()).thenReturn(System.currentTimeMillis() / 1000L);
when(state.updatedAt()).thenReturn(System.currentTimeMillis() / 1000L);
when(state.keySet()).thenReturn(Collections.singleton("foo"));
when(state.get("foo")).thenReturn("bar");
ParseObject parseObject = ParseObject.from(state);

ParseRESTObjectCommand command =
ParseRESTObjectCommand.saveObjectCommand(parseObject.getState(), null, null);
assertEquals(command.method, ParseHttpRequest.Method.PUT);

Parse.Configuration configuration =
new Parse.Configuration.Builder(RuntimeEnvironment.application)
.applicationId(BuildConfig.LIBRARY_PACKAGE_NAME)
.server("https://api.parse.com/1")
.enableLocalDataStore()
.allowCustomObjectId()
.build();
ParsePlugins plugins = mock(ParsePlugins.class);
when(plugins.configuration()).thenReturn(configuration);
when(plugins.applicationContext()).thenReturn(RuntimeEnvironment.application);
Parse.initialize(configuration, plugins);

command = ParseRESTObjectCommand.saveObjectCommand(parseObject.getState(), null, null);
assertEquals(command.method, ParseHttpRequest.Method.PUT);

ParseCorePlugins.getInstance().reset();
ParsePlugins.reset();
Parse.destroy();
}
}