Merge pull request #2026 from OpenRefine/issue-1990
Fix JSON history corruption.
This commit is contained in:
commit
98dffc4fdf
@ -45,6 +45,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
|||||||
import com.google.refine.commands.Command;
|
import com.google.refine.commands.Command;
|
||||||
import com.google.refine.model.AbstractOperation;
|
import com.google.refine.model.AbstractOperation;
|
||||||
import com.google.refine.model.Project;
|
import com.google.refine.model.Project;
|
||||||
|
import com.google.refine.operations.UnknownOperation;
|
||||||
import com.google.refine.process.Process;
|
import com.google.refine.process.Process;
|
||||||
import com.google.refine.util.ParsingUtilities;
|
import com.google.refine.util.ParsingUtilities;
|
||||||
|
|
||||||
@ -79,7 +80,7 @@ public class ApplyOperationsCommand extends Command {
|
|||||||
|
|
||||||
protected void reconstructOperation(Project project, ObjectNode obj) throws IOException {
|
protected void reconstructOperation(Project project, ObjectNode obj) throws IOException {
|
||||||
AbstractOperation operation = ParsingUtilities.mapper.convertValue(obj, AbstractOperation.class);
|
AbstractOperation operation = ParsingUtilities.mapper.convertValue(obj, AbstractOperation.class);
|
||||||
if (operation != null) {
|
if (operation != null && !(operation instanceof UnknownOperation)) {
|
||||||
try {
|
try {
|
||||||
Process process = operation.createProcess(project, new Properties());
|
Process process = operation.createProcess(project, new Properties());
|
||||||
|
|
||||||
|
@ -64,7 +64,11 @@ public class FileHistoryEntryManager implements HistoryEntryManager{
|
|||||||
@Override
|
@Override
|
||||||
public void save(HistoryEntry historyEntry, Writer writer, Properties options) {
|
public void save(HistoryEntry historyEntry, Writer writer, Properties options) {
|
||||||
try {
|
try {
|
||||||
|
if("save".equals(options.getProperty("mode"))) {
|
||||||
|
ParsingUtilities.saveWriter.writeValue(writer, historyEntry);
|
||||||
|
} else {
|
||||||
ParsingUtilities.defaultWriter.writeValue(writer, historyEntry);
|
ParsingUtilities.defaultWriter.writeValue(writer, historyEntry);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,8 @@ import com.google.refine.process.QuickHistoryEntryProcess;
|
|||||||
@JsonTypeInfo(
|
@JsonTypeInfo(
|
||||||
use=JsonTypeInfo.Id.CUSTOM,
|
use=JsonTypeInfo.Id.CUSTOM,
|
||||||
include=JsonTypeInfo.As.PROPERTY,
|
include=JsonTypeInfo.As.PROPERTY,
|
||||||
property="op")
|
property="op",
|
||||||
|
visible=true) // for UnknownOperation, which needs to read its own id
|
||||||
@JsonTypeIdResolver(OperationResolver.class)
|
@JsonTypeIdResolver(OperationResolver.class)
|
||||||
abstract public class AbstractOperation {
|
abstract public class AbstractOperation {
|
||||||
public Process createProcess(Project project, Properties options) throws Exception {
|
public Process createProcess(Project project, Properties options) throws Exception {
|
||||||
|
@ -51,11 +51,20 @@ public class OperationResolver extends TypeIdResolverBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String idFromValueAndType(Object instance, Class<?> type) {
|
public String idFromValueAndType(Object instance, Class<?> type) {
|
||||||
return OperationRegistry.s_opClassToName.get(type);
|
String id = OperationRegistry.s_opClassToName.get(type);
|
||||||
|
if (id != null) {
|
||||||
|
return id;
|
||||||
|
} else { // this happens for an UnknownOperation
|
||||||
|
return ((AbstractOperation) instance).getOperationId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaType typeFromId(DatabindContext context, String id) throws IOException {
|
public JavaType typeFromId(DatabindContext context, String id) throws IOException {
|
||||||
return factory.constructSimpleType(OperationRegistry.resolveOperationId(id), new JavaType[0]);
|
Class<? extends AbstractOperation> opClass = OperationRegistry.resolveOperationId(id);
|
||||||
|
if (opClass == null) {
|
||||||
|
opClass = UnknownOperation.class;
|
||||||
|
}
|
||||||
|
return factory.constructSimpleType(opClass, new JavaType[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
62
main/src/com/google/refine/operations/UnknownOperation.java
Normal file
62
main/src/com/google/refine/operations/UnknownOperation.java
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package com.google.refine.operations;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.google.refine.model.AbstractOperation;
|
||||||
|
import com.google.refine.model.Project;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An operation that is unknown to the current OpenRefine
|
||||||
|
* instance, but might be interpretable by another instance
|
||||||
|
* (for instance, a later version of OpenRefine, or using
|
||||||
|
* an extension).
|
||||||
|
*
|
||||||
|
* This class holds the JSON serialization of the operation,
|
||||||
|
* in the interest of being able to serialize it later, hence
|
||||||
|
* avoiding to discard it and lose metadata.
|
||||||
|
*
|
||||||
|
* @author Antonin Delpeuch
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class UnknownOperation extends AbstractOperation {
|
||||||
|
|
||||||
|
// Map storing the JSON serialization of the operation in an agnostic way
|
||||||
|
private Map<String, Object> properties;
|
||||||
|
|
||||||
|
// Operation code and description stored separately
|
||||||
|
private String opCode;
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public UnknownOperation(
|
||||||
|
@JsonProperty("op") String opCode,
|
||||||
|
@JsonProperty("description") String description) {
|
||||||
|
properties = new HashMap<>();
|
||||||
|
this.opCode = opCode;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonAnySetter
|
||||||
|
public void setAttribute(String key, Object value) {
|
||||||
|
properties.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonAnyGetter
|
||||||
|
public Map<String,Object> getAttributes() {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty("op")
|
||||||
|
public String getOperationId() {
|
||||||
|
return opCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getBriefDescription(Project project) {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package com.google.refine.tests.history;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.testng.annotations.BeforeMethod;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import com.google.refine.history.HistoryEntry;
|
||||||
|
import com.google.refine.io.FileHistoryEntryManager;
|
||||||
|
import com.google.refine.model.Project;
|
||||||
|
import com.google.refine.operations.OperationRegistry;
|
||||||
|
import com.google.refine.operations.column.ColumnAdditionOperation;
|
||||||
|
import com.google.refine.tests.RefineTest;
|
||||||
|
import com.google.refine.tests.util.TestUtils;
|
||||||
|
|
||||||
|
public class FileHistoryEntryManagerTests extends RefineTest {
|
||||||
|
|
||||||
|
Project project;
|
||||||
|
FileHistoryEntryManager sut = new FileHistoryEntryManager();
|
||||||
|
|
||||||
|
@BeforeMethod
|
||||||
|
public void setUp() {
|
||||||
|
project = mock(Project.class);
|
||||||
|
OperationRegistry.registerOperation(getCoreModule(), "column-addition", ColumnAdditionOperation.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteHistoryEntry() throws IOException {
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
HistoryEntry historyEntry = HistoryEntry.load(project, HistoryEntryTests.fullJson);
|
||||||
|
Properties options = new Properties();
|
||||||
|
options.setProperty("mode", "save");
|
||||||
|
sut.save(historyEntry, writer, options);
|
||||||
|
TestUtils.equalAsJson(HistoryEntryTests.fullJson, writer.toString());
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,8 @@ package com.google.refine.tests.history;
|
|||||||
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.testng.annotations.BeforeMethod;
|
import org.testng.annotations.BeforeMethod;
|
||||||
import org.testng.annotations.BeforeTest;
|
import org.testng.annotations.BeforeTest;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
@ -41,6 +43,30 @@ import com.google.refine.tests.util.TestUtils;
|
|||||||
|
|
||||||
public class HistoryEntryTests extends RefineTest {
|
public class HistoryEntryTests extends RefineTest {
|
||||||
|
|
||||||
|
public static final String fullJson = "{"
|
||||||
|
+ "\"id\":1533633623158,"
|
||||||
|
+ "\"description\":\"Create new column uri based on column country by filling 269 rows with grel:\\\"https://www.wikidata.org/wiki/\\\"+cell.recon.match.id\","
|
||||||
|
+ "\"time\":\"2018-08-07T09:06:37Z\","
|
||||||
|
+ "\"operation\":{\"op\":\"core/column-addition\","
|
||||||
|
+ " \"description\":\"Create column uri at index 2 based on column country using expression grel:\\\"https://www.wikidata.org/wiki/\\\"+cell.recon.match.id\","
|
||||||
|
+ " \"engineConfig\":{\"mode\":\"row-based\",\"facets\":[]},"
|
||||||
|
+ " \"newColumnName\":\"uri\","
|
||||||
|
+ " \"columnInsertIndex\":2,"
|
||||||
|
+ " \"baseColumnName\":\"country\","
|
||||||
|
+ " \"expression\":\"grel:\\\"https://www.wikidata.org/wiki/\\\"+cell.recon.match.id\","
|
||||||
|
+ " \"onError\":\"set-to-blank\"}"
|
||||||
|
+ "}";
|
||||||
|
|
||||||
|
public static final String unknownOperationJson = "{"
|
||||||
|
+ "\"id\":1533633623158,"
|
||||||
|
+ "\"description\":\"some mysterious operation\","
|
||||||
|
+ "\"time\":\"2018-08-07T09:06:37Z\","
|
||||||
|
+ "\"operation\":{\"op\":\"someextension/unknown-operation\","
|
||||||
|
+ " \"description\":\"some mysterious operation\","
|
||||||
|
+ " \"some_parameter\":234\n"
|
||||||
|
+ "}\n"
|
||||||
|
+ "}";
|
||||||
|
|
||||||
Project project;
|
Project project;
|
||||||
|
|
||||||
@BeforeTest
|
@BeforeTest
|
||||||
@ -63,26 +89,20 @@ public class HistoryEntryTests extends RefineTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void serializeHistoryEntryWithOperation() throws Exception {
|
public void serializeHistoryEntryWithOperation() throws Exception {
|
||||||
String json = "{"
|
|
||||||
+ "\"id\":1533633623158,"
|
|
||||||
+ "\"description\":\"Create new column uri based on column country by filling 269 rows with grel:\\\"https://www.wikidata.org/wiki/\\\"+cell.recon.match.id\","
|
|
||||||
+ "\"time\":\"2018-08-07T09:06:37Z\","
|
|
||||||
+ "\"operation\":{\"op\":\"core/column-addition\","
|
|
||||||
+ " \"description\":\"Create column uri at index 2 based on column country using expression grel:\\\"https://www.wikidata.org/wiki/\\\"+cell.recon.match.id\","
|
|
||||||
+ " \"engineConfig\":{\"mode\":\"row-based\",\"facets\":[]},"
|
|
||||||
+ " \"newColumnName\":\"uri\","
|
|
||||||
+ " \"columnInsertIndex\":2,"
|
|
||||||
+ " \"baseColumnName\":\"country\","
|
|
||||||
+ " \"expression\":\"grel:\\\"https://www.wikidata.org/wiki/\\\"+cell.recon.match.id\","
|
|
||||||
+ " \"onError\":\"set-to-blank\"}"
|
|
||||||
+ "}";
|
|
||||||
String jsonSimple = "{"
|
String jsonSimple = "{"
|
||||||
+ "\"id\":1533633623158,"
|
+ "\"id\":1533633623158,"
|
||||||
+ "\"description\":\"Create new column uri based on column country by filling 269 rows with grel:\\\"https://www.wikidata.org/wiki/\\\"+cell.recon.match.id\","
|
+ "\"description\":\"Create new column uri based on column country by filling 269 rows with grel:\\\"https://www.wikidata.org/wiki/\\\"+cell.recon.match.id\","
|
||||||
+ "\"time\":\"2018-08-07T09:06:37Z\"}";
|
+ "\"time\":\"2018-08-07T09:06:37Z\"}";
|
||||||
|
|
||||||
HistoryEntry historyEntry = HistoryEntry.load(project, json);
|
HistoryEntry historyEntry = HistoryEntry.load(project, fullJson);
|
||||||
TestUtils.isSerializedTo(historyEntry, jsonSimple, false);
|
TestUtils.isSerializedTo(historyEntry, jsonSimple, false);
|
||||||
TestUtils.isSerializedTo(historyEntry, json, true);
|
TestUtils.isSerializedTo(historyEntry, fullJson, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deserializeUnknownOperation() throws IOException {
|
||||||
|
// Unknown operations are serialized back as they were parsed
|
||||||
|
HistoryEntry entry = HistoryEntry.load(project, unknownOperationJson);
|
||||||
|
TestUtils.isSerializedTo(entry, unknownOperationJson, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user