Skip to content

Commit

Permalink
feat: add support for custom objectId (#1088)
Browse files Browse the repository at this point in the history
  • Loading branch information
martinpfannemueller committed Nov 21, 2021
1 parent bd3ac1d commit d420371
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 8 deletions.
29 changes: 29 additions & 0 deletions parse/src/main/java/com/parse/Parse.java
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
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
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
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
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
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();
}
}

0 comments on commit d420371

Please sign in to comment.