Merge pull request #2207 from OpenRefine/issue-2206-double-creation
Fix duplicate creations of Wikidata items
This commit is contained in:
commit
286915ed83
@ -31,6 +31,7 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.openrefine.wikidata.schema.entityvalues.ReconEntityIdValue;
|
||||
import org.openrefine.wikidata.schema.exceptions.NewItemNotCreatedYetException;
|
||||
import org.openrefine.wikidata.updates.ItemUpdate;
|
||||
import org.openrefine.wikidata.updates.scheduler.WikibaseAPIUpdateScheduler;
|
||||
import org.slf4j.Logger;
|
||||
@ -128,7 +129,12 @@ public class EditBatchProcessor {
|
||||
|
||||
// Rewrite mentions to new items
|
||||
ReconEntityRewriter rewriter = new ReconEntityRewriter(library, update.getItemId());
|
||||
try {
|
||||
update = rewriter.rewrite(update);
|
||||
} catch (NewItemNotCreatedYetException e) {
|
||||
logger.warn("Failed to rewrite update on entity "+update.getItemId()+". Missing entity: "+e.getMissingEntity()+". Skipping update.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// New item
|
||||
|
@ -28,10 +28,12 @@ import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.openrefine.wikidata.schema.entityvalues.ReconItemIdValue;
|
||||
import org.openrefine.wikidata.schema.exceptions.NewItemNotCreatedYetException;
|
||||
import org.openrefine.wikidata.updates.ItemUpdate;
|
||||
import org.wikidata.wdtk.datamodel.helpers.Datamodel;
|
||||
import org.wikidata.wdtk.datamodel.helpers.DatamodelConverter;
|
||||
import org.wikidata.wdtk.datamodel.implementation.DataObjectFactoryImpl;
|
||||
import org.wikidata.wdtk.datamodel.interfaces.EntityIdValue;
|
||||
import org.wikidata.wdtk.datamodel.interfaces.ItemIdValue;
|
||||
import org.wikidata.wdtk.datamodel.interfaces.MonolingualTextValue;
|
||||
import org.wikidata.wdtk.datamodel.interfaces.Statement;
|
||||
@ -53,15 +55,19 @@ import org.wikidata.wdtk.datamodel.interfaces.Statement;
|
||||
*/
|
||||
public class ReconEntityRewriter extends DatamodelConverter {
|
||||
|
||||
private NewItemLibrary library;
|
||||
private ItemIdValue subject;
|
||||
private final NewItemLibrary library;
|
||||
private final ItemIdValue subject;
|
||||
|
||||
protected static final String notCreatedYetMessage = "Trying to rewrite an update where a new item was not created yet.";
|
||||
|
||||
/**
|
||||
* Constructor. Sets up a rewriter which uses the provided library to look up
|
||||
* qids of new items, and the subject (which should not be rewritten).
|
||||
* qids of new items.
|
||||
*
|
||||
* @param library
|
||||
* the collection of items already created
|
||||
* @param subject
|
||||
* the subject id of the entity to rewrite
|
||||
*/
|
||||
public ReconEntityRewriter(NewItemLibrary library, ItemIdValue subject) {
|
||||
super(new DataObjectFactoryImpl());
|
||||
@ -71,16 +77,16 @@ public class ReconEntityRewriter extends DatamodelConverter {
|
||||
|
||||
@Override
|
||||
public ItemIdValue copy(ItemIdValue value) {
|
||||
if (subject.equals(value)) {
|
||||
return value;
|
||||
}
|
||||
if (value instanceof ReconItemIdValue) {
|
||||
ReconItemIdValue recon = (ReconItemIdValue) value;
|
||||
if (recon.isNew()) {
|
||||
String newId = library.getQid(recon.getReconInternalId());
|
||||
if (newId == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Trying to rewrite an update where a new item was not created yet.");
|
||||
if (subject.equals(recon)) {
|
||||
return subject;
|
||||
} else {
|
||||
throw new MissingEntityIdFound(recon);
|
||||
}
|
||||
}
|
||||
return Datamodel.makeItemIdValue(newId, recon.getRecon().identifierSpace);
|
||||
}
|
||||
@ -88,7 +94,22 @@ public class ReconEntityRewriter extends DatamodelConverter {
|
||||
return super.copy(value);
|
||||
}
|
||||
|
||||
public ItemUpdate rewrite(ItemUpdate update) {
|
||||
/**
|
||||
* Rewrite an update, replacing references to all entities already
|
||||
* created by their fresh identifiers. The subject id might not have been
|
||||
* created already, in which case it will be left untouched. All the other
|
||||
* entities need to have been created already.
|
||||
*
|
||||
* @param update
|
||||
* the update to rewrite
|
||||
* @return
|
||||
* the rewritten update
|
||||
* @throws NewItemNotCreatedYetException
|
||||
* if any non-subject entity had not been created yet
|
||||
*/
|
||||
public ItemUpdate rewrite(ItemUpdate update) throws NewItemNotCreatedYetException {
|
||||
try {
|
||||
ItemIdValue subject = copy(update.getItemId());
|
||||
Set<MonolingualTextValue> labels = update.getLabels().stream().map(l -> copy(l)).collect(Collectors.toSet());
|
||||
Set<MonolingualTextValue> labelsIfNew = update.getLabelsIfNew().stream().map(l -> copy(l)).collect(Collectors.toSet());
|
||||
Set<MonolingualTextValue> descriptions = update.getDescriptions().stream().map(l -> copy(l))
|
||||
@ -100,6 +121,21 @@ public class ReconEntityRewriter extends DatamodelConverter {
|
||||
.collect(Collectors.toList());
|
||||
Set<Statement> deletedStatements = update.getDeletedStatements().stream().map(l -> copy(l))
|
||||
.collect(Collectors.toSet());
|
||||
return new ItemUpdate(update.getItemId(), addedStatements, deletedStatements, labels, labelsIfNew, descriptions, descriptionsIfNew, aliases);
|
||||
return new ItemUpdate(subject, addedStatements, deletedStatements, labels, labelsIfNew, descriptions, descriptionsIfNew, aliases);
|
||||
} catch(MissingEntityIdFound e) {
|
||||
throw new NewItemNotCreatedYetException(e.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unchecked version of {@class NewItemNotCreatedYetException}, for internal use only.
|
||||
*/
|
||||
protected static class MissingEntityIdFound extends Error {
|
||||
private static final long serialVersionUID = 1L;
|
||||
protected EntityIdValue value;
|
||||
public MissingEntityIdFound(EntityIdValue missing) {
|
||||
this.value = missing;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
package org.openrefine.wikidata.schema.exceptions;
|
||||
|
||||
import org.wikidata.wdtk.datamodel.interfaces.EntityIdValue;
|
||||
|
||||
public class NewItemNotCreatedYetException extends Exception {
|
||||
private static final long serialVersionUID = -563535295696710197L;
|
||||
|
||||
private final EntityIdValue value;
|
||||
|
||||
public NewItemNotCreatedYetException(EntityIdValue value) {
|
||||
super("Attempted to rewrite an entity which was not created yet: "+value);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public EntityIdValue getMissingEntity() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
@ -407,13 +407,21 @@ public class ItemUpdate {
|
||||
builder.append("<Update on ");
|
||||
builder.append(qid);
|
||||
if (!labels.isEmpty()) {
|
||||
builder.append("\n Labels: ");
|
||||
builder.append("\n Labels (override): ");
|
||||
builder.append(labels);
|
||||
}
|
||||
if (!labelsIfNew.isEmpty()) {
|
||||
builder.append("\n Labels (if new): ");
|
||||
builder.append(labelsIfNew);
|
||||
}
|
||||
if (!descriptions.isEmpty()) {
|
||||
builder.append("\n Descriptions: ");
|
||||
builder.append("\n Descriptions (override): ");
|
||||
builder.append(descriptions);
|
||||
}
|
||||
if (!descriptionsIfNew.isEmpty()) {
|
||||
builder.append("\n Descriptions (if new): ");
|
||||
builder.append(descriptionsIfNew);
|
||||
}
|
||||
if (!aliases.isEmpty()) {
|
||||
builder.append("\n Aliases: ");
|
||||
builder.append(aliases);
|
||||
|
@ -25,6 +25,7 @@ package org.openrefine.wikidata.editing;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import org.openrefine.wikidata.schema.exceptions.NewItemNotCreatedYetException;
|
||||
import org.openrefine.wikidata.testing.TestingData;
|
||||
import org.openrefine.wikidata.updates.ItemUpdate;
|
||||
import org.openrefine.wikidata.updates.ItemUpdateBuilder;
|
||||
@ -37,38 +38,51 @@ public class ReconEntityRewriterTest {
|
||||
|
||||
NewItemLibrary library = null;
|
||||
ReconEntityRewriter rewriter = null;
|
||||
ItemIdValue subject = TestingData.newIdA;
|
||||
ItemIdValue newlyCreated = Datamodel.makeWikidataItemIdValue("Q1234");
|
||||
|
||||
@BeforeMethod
|
||||
public void setUp() {
|
||||
library = new NewItemLibrary();
|
||||
rewriter = new ReconEntityRewriter(library, subject);
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = IllegalArgumentException.class)
|
||||
@Test(expectedExceptions = ReconEntityRewriter.MissingEntityIdFound.class)
|
||||
public void testNotCreatedYet() {
|
||||
rewriter = new ReconEntityRewriter(library, TestingData.newIdA);
|
||||
rewriter.copy(TestingData.newIdB);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulRewrite() {
|
||||
rewriter = new ReconEntityRewriter(library, TestingData.newIdA);
|
||||
library.setQid(4567L, "Q1234");
|
||||
assertEquals(newlyCreated, rewriter.copy(TestingData.newIdB));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubjectNotRewriten() {
|
||||
public void testSubjectNotRewritten() {
|
||||
ItemIdValue subject = TestingData.newIdA;
|
||||
rewriter = new ReconEntityRewriter(library, subject);
|
||||
assertEquals(subject, rewriter.copy(subject));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubjectRewritten() {
|
||||
ItemIdValue subject = TestingData.newIdB;
|
||||
library.setQid(4567L, "Q1234");
|
||||
rewriter = new ReconEntityRewriter(library, subject);
|
||||
assertEquals(newlyCreated, rewriter.copy(subject));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatched() {
|
||||
rewriter = new ReconEntityRewriter(library, TestingData.newIdA);
|
||||
assertEquals(TestingData.matchedId, rewriter.copy(TestingData.matchedId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRewriteUpdate() {
|
||||
public void testRewriteCreate() throws NewItemNotCreatedYetException {
|
||||
ItemIdValue subject = TestingData.newIdA;
|
||||
rewriter = new ReconEntityRewriter(library, subject);
|
||||
library.setQid(4567L, "Q1234");
|
||||
ItemUpdate update = new ItemUpdateBuilder(subject)
|
||||
.addStatement(TestingData.generateStatement(subject, TestingData.newIdB))
|
||||
@ -83,6 +97,42 @@ public class ReconEntityRewriterTest {
|
||||
.addLabel(Datamodel.makeMonolingualTextValue("label", "de"), true)
|
||||
.addDescription(Datamodel.makeMonolingualTextValue("beschreibung", "de"), false)
|
||||
.addAlias(Datamodel.makeMonolingualTextValue("darstellung", "de")).build();
|
||||
assertEquals(expected, rewritten);
|
||||
assertEquals(rewritten, expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRewriteUpdateOnPreviouslyCreatedEntity() throws NewItemNotCreatedYetException {
|
||||
ItemIdValue subject = TestingData.newIdA;
|
||||
rewriter = new ReconEntityRewriter(library, subject);
|
||||
library.setQid(4567L, "Q1234");
|
||||
ItemUpdate update = new ItemUpdateBuilder(TestingData.newIdB)
|
||||
.addDescription(Datamodel.makeMonolingualTextValue("beschreibung", "de"), false)
|
||||
.addAlias(Datamodel.makeMonolingualTextValue("darstellung", "de")).build();
|
||||
ItemUpdate rewritten = rewriter.rewrite(update);
|
||||
ItemUpdate expected = new ItemUpdateBuilder(newlyCreated)
|
||||
.addDescription(Datamodel.makeMonolingualTextValue("beschreibung", "de"), false)
|
||||
.addAlias(Datamodel.makeMonolingualTextValue("darstellung", "de")).build();
|
||||
assertEquals(rewritten, expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRewriteUpdateOnExistingEntity() throws NewItemNotCreatedYetException {
|
||||
ItemIdValue subject = TestingData.matchedId;
|
||||
rewriter = new ReconEntityRewriter(library, subject);
|
||||
library.setQid(4567L, "Q1234");
|
||||
ItemUpdate update = new ItemUpdateBuilder(subject)
|
||||
.addStatement(TestingData.generateStatement(subject, TestingData.newIdB))
|
||||
.deleteStatement(TestingData.generateStatement(subject, TestingData.existingId))
|
||||
.addLabel(Datamodel.makeMonolingualTextValue("label", "de"), true)
|
||||
.addDescription(Datamodel.makeMonolingualTextValue("beschreibung", "de"), false)
|
||||
.addAlias(Datamodel.makeMonolingualTextValue("darstellung", "de")).build();
|
||||
ItemUpdate rewritten = rewriter.rewrite(update);
|
||||
ItemUpdate expected = new ItemUpdateBuilder(subject)
|
||||
.addStatement(TestingData.generateStatement(subject, newlyCreated))
|
||||
.deleteStatement(TestingData.generateStatement(subject, TestingData.existingId))
|
||||
.addLabel(Datamodel.makeMonolingualTextValue("label", "de"), true)
|
||||
.addDescription(Datamodel.makeMonolingualTextValue("beschreibung", "de"), false)
|
||||
.addAlias(Datamodel.makeMonolingualTextValue("darstellung", "de")).build();
|
||||
assertEquals(rewritten, expected);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user