Skip to content

Commit 6e8804c

Browse files
hermanliangrogerhu
authored andcommitted
Re-save installation is allowed if LDS is enabled (#607)
* Allowing to re-save installation if LDS is enabled * handle MissingRequiredFieldError exception (code 135)
1 parent 2659e7d commit 6e8804c

File tree

4 files changed

+80
-21
lines changed

4 files changed

+80
-21
lines changed

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

+11-1
Original file line numberDiff line numberDiff line change
@@ -1456,7 +1456,17 @@ public Integer then(Task<List<T>> task) throws Exception {
14561456
if (oldObjectId.equals(newObjectId)) {
14571457
return;
14581458
}
1459-
throw new RuntimeException("objectIds cannot be changed in offline mode.");
1459+
/**
1460+
* Special case for re-saving installation if it was deleted on the server
1461+
* @see ParseInstallation#saveAsync(String, Task)
1462+
*/
1463+
if (object instanceof ParseInstallation
1464+
&& newObjectId == null) {
1465+
classNameAndObjectIdToObjectMap.remove(Pair.create(object.getClassName(), oldObjectId));
1466+
return;
1467+
} else {
1468+
throw new RuntimeException("objectIds cannot be changed in offline mode.");
1469+
}
14601470
}
14611471

14621472
String className = object.getClassName();

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

+5
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ public class ParseException extends Exception {
134134
*/
135135
public static final int INVALID_EMAIL_ADDRESS = 125;
136136

137+
/**
138+
* Error code indicating that required field is missing.
139+
*/
140+
public static final int MISSING_REQUIRED_FIELD_ERROR = 135;
141+
137142
/**
138143
* Error code indicating that a unique field was given a value that is already taken.
139144
*/

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

+10-9
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,16 @@ public Task<T> then(Task<Void> task) throws Exception {
149149
@Override
150150
public Task<Void> then(Task<Void> task) throws Exception {
151151
// Retry the fetch as a save operation because this Installation was deleted on the server.
152-
// Do not attempt to resave an object if LDS is enabled, since changing objectId is not allowed.
153-
if(!Parse.isLocalDatastoreEnabled()
154-
&& task.getError() != null
155-
&& task.getError() instanceof ParseException
156-
&& ((ParseException) task.getError()).getCode() == ParseException.OBJECT_NOT_FOUND) {
157-
synchronized (mutex) {
158-
setState(new State.Builder(getState()).objectId(null).build());
159-
markAllFieldsDirty();
160-
return ParseInstallation.super.saveAsync(sessionToken, toAwait);
152+
if (task.getError() != null
153+
&& task.getError() instanceof ParseException) {
154+
int errCode = ((ParseException) task.getError()).getCode();
155+
if (errCode == ParseException.OBJECT_NOT_FOUND
156+
|| (errCode == ParseException.MISSING_REQUIRED_FIELD_ERROR && getObjectId() == null)) {
157+
synchronized (mutex) {
158+
setState(new State.Builder(getState()).objectId(null).build());
159+
markAllFieldsDirty();
160+
return ParseInstallation.super.saveAsync(sessionToken, toAwait);
161+
}
161162
}
162163
}
163164
return task;

Parse/src/test/java/com/parse/ParseInstallationTest.java

+54-11
Original file line numberDiff line numberDiff line change
@@ -121,24 +121,48 @@ public void testInstallationObjectIdCannotBeChanged() throws Exception {
121121
}
122122

123123
@Test
124-
public void testSaveAsync() throws Exception {
124+
public void testMissingRequiredFieldWhenSaveAsync() throws Exception {
125125
String sessionToken = "sessionToken";
126126
Task<Void> toAwait = Task.forResult(null);
127127

128-
ParseCurrentInstallationController controller =
129-
mock(ParseCurrentInstallationController.class);
130-
ParseInstallation currentInstallation = new ParseInstallation();
131-
when(controller.getAsync())
132-
.thenReturn(Task.forResult(currentInstallation));
128+
ParseCurrentInstallationController controller = mockCurrentInstallationController();
129+
130+
ParseObjectController objController = mock(ParseObjectController.class);
131+
// mock return task when Installation was deleted on the server
132+
Task<ParseObject.State> taskError = Task.forError(new ParseException(ParseException.MISSING_REQUIRED_FIELD_ERROR, ""));
133+
// mock return task when Installation was re-saved to the server
134+
Task<ParseObject.State> task = Task.forResult(null);
135+
when(objController.saveAsync(
136+
any(ParseObject.State.class),
137+
any(ParseOperationSet.class),
138+
eq(sessionToken),
139+
any(ParseDecoder.class)))
140+
.thenReturn(taskError)
141+
.thenReturn(task);
133142
ParseCorePlugins.getInstance()
134-
.registerCurrentInstallationController(controller);
135-
ParseObject.State state = new ParseObject.State.Builder("_Installation")
136-
.put("deviceToken", "deviceToken")
137-
.build();
143+
.registerObjectController(objController);
144+
138145
ParseInstallation installation = ParseInstallation.getCurrentInstallation();
139146
assertNotNull(installation);
140-
installation.setState(state);
147+
installation.put("key", "value");
148+
installation.saveAsync(sessionToken, toAwait);
149+
verify(controller).getAsync();
150+
verify(objController, times(2)).saveAsync(
151+
any(ParseObject.State.class),
152+
any(ParseOperationSet.class),
153+
eq(sessionToken),
154+
any(ParseDecoder.class));
155+
}
141156

157+
@Test
158+
public void testObjectNotFoundWhenSaveAsync() throws Exception {
159+
OfflineStore lds = new OfflineStore(RuntimeEnvironment.application);
160+
Parse.setLocalDatastore(lds);
161+
162+
String sessionToken = "sessionToken";
163+
Task<Void> toAwait = Task.forResult(null);
164+
165+
ParseCurrentInstallationController controller = mockCurrentInstallationController();
142166
ParseObjectController objController = mock(ParseObjectController.class);
143167
// mock return task when Installation was deleted on the server
144168
Task<ParseObject.State> taskError = Task.forError(new ParseException(ParseException.OBJECT_NOT_FOUND, ""));
@@ -154,6 +178,14 @@ public void testSaveAsync() throws Exception {
154178
ParseCorePlugins.getInstance()
155179
.registerObjectController(objController);
156180

181+
ParseObject.State state = new ParseObject.State.Builder("_Installation")
182+
.objectId("oldId")
183+
.put("deviceToken", "deviceToken")
184+
.build();
185+
ParseInstallation installation = ParseInstallation.getCurrentInstallation();
186+
assertNotNull(installation);
187+
installation.setState(state);
188+
installation.put("key", "value");
157189
installation.saveAsync(sessionToken, toAwait);
158190

159191
verify(controller).getAsync();
@@ -355,4 +387,15 @@ private static void mocksForUpdateBeforeSave() {
355387
when(plugins.applicationContext()).thenReturn(RuntimeEnvironment.application);
356388
ParsePlugins.set(plugins);
357389
}
390+
391+
private ParseCurrentInstallationController mockCurrentInstallationController() {
392+
ParseCurrentInstallationController controller =
393+
mock(ParseCurrentInstallationController.class);
394+
ParseInstallation currentInstallation = new ParseInstallation();
395+
when(controller.getAsync())
396+
.thenReturn(Task.forResult(currentInstallation));
397+
ParseCorePlugins.getInstance()
398+
.registerCurrentInstallationController(controller);
399+
return controller;
400+
}
358401
}

0 commit comments

Comments
 (0)