Repository: Wikidata/Wikidata-Toolkit
Pull request #464 (2019-11-20 16:21:57)

Adds MediaInfo retrieval and editing using the API


Patch:
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/JsonSerializer.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/JsonSerializer.java
index 1749978b..2259c7e8 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/JsonSerializer.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/JsonSerializer.java
@@ -190,6 +190,18 @@ public class JsonSerializer implements EntityDocumentDumpProcessor {
 		return jacksonObjectToString(propertyDocument);
 	}
 
+	/**
+	 * Serializes the given object in JSON and returns the resulting string. In
+	 * case of errors, null is returned.
+	 *
+	 * @param mediaInfoDocument
+	 *            object to serialize
+	 * @return JSON serialization or null
+	 */
+	public static String getJsonString(MediaInfoDocument mediaInfoDocument) {
+		return jacksonObjectToString(mediaInfoDocument);
+	}
+
 	/**
 	 * Serializes the given object in JSON and returns the resulting string. In
 	 * case of errors, null is returned.
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/EntityIdValueImpl.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/EntityIdValueImpl.java
index af5ed980..752b4b10 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/EntityIdValueImpl.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/EntityIdValueImpl.java
@@ -119,7 +119,7 @@ public abstract class EntityIdValueImpl extends ValueImpl implements
 	 * @throws IllegalArgumentException
 	 *      if the id is invalid
 	 */
-	static EntityIdValue fromId(String id, String siteIri) {
+	public static EntityIdValue fromId(String id, String siteIri) {
 		switch (guessEntityTypeFromId(id)) {
 			case EntityIdValueImpl.JSON_ENTITY_TYPE_ITEM:
 				return new ItemIdValueImpl(id, siteIri);
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoDocumentImpl.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoDocumentImpl.java
index 88764d6e..6b365ce5 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoDocumentImpl.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoDocumentImpl.java
@@ -32,6 +32,9 @@ import java.util.*;
 
 /**
  * Jackson implementation of {@link MediaInfoDocument}.
+ * <p>
+ * We serialize back statements in JSON with the "claims" key for compatibility with the other entity types
+ * and the wbeditentity API mdodule
  *
  * @author Thomas Pellissier Tanon
  */
@@ -65,8 +68,7 @@ public class MediaInfoDocumentImpl extends LabeledStatementDocumentImpl implemen
 	 * Constructor. Creates an object that can be populated during JSON
 	 * deserialization. Should only be used by Jackson for this very purpose.
 	 *
-	 * The claims parameter is here in case the Structured Data on Commons dev team
-	 * moves back from "statements" to "claims" or someone wrongly use the "claims" key.
+	 * The claims parameter is here for compatibility with wbeditentity.
 	 */
 	@JsonCreator
 	public MediaInfoDocumentImpl(
@@ -97,17 +99,6 @@ public class MediaInfoDocumentImpl extends LabeledStatementDocumentImpl implemen
 		return new MediaInfoIdValueImpl(this.entityId, this.siteIri);
 	}
 
-	@JsonProperty("type")
-	String getType() {
-		return EntityDocumentImpl.JSON_TYPE_MEDIA_INFO;
-	}
-
-	@JsonProperty("statements")
-	@Override
-	public Map<String, List<Statement>> getJsonClaims() {
-		return super.getJsonClaims();
-	}
-
 	@Override
 	public int hashCode() {
 		return Hash.hashCode(this);
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/MediaInfoIdValue.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/MediaInfoIdValue.java
index d232139f..5982c93d 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/MediaInfoIdValue.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/MediaInfoIdValue.java
@@ -12,9 +12,9 @@ import org.wikidata.wdtk.datamodel.helpers.Hash;
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *      http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -63,12 +63,12 @@ public interface MediaInfoIdValue extends EntityIdValue {
 		public String getSiteIri() {
 			return EntityIdValue.SITE_LOCAL;
 		}
-		
+
 		@Override
 		public boolean equals(Object other) {
 			return Equality.equalsEntityIdValue(this, other);
 		}
-		
+
 		@Override
 		public int hashCode() {
 			return Hash.hashCode(this);
diff --git a/wdtk-examples/src/main/java/org/wikidata/wdtk/examples/EditOnlineMediaInfoExample.java b/wdtk-examples/src/main/java/org/wikidata/wdtk/examples/EditOnlineMediaInfoExample.java
new file mode 100644
index 00000000..b8668337
--- /dev/null
+++ b/wdtk-examples/src/main/java/org/wikidata/wdtk/examples/EditOnlineMediaInfoExample.java
@@ -0,0 +1,95 @@
+package org.wikidata.wdtk.examples;
+
+/*
+ * #%L
+ * Wikidata Toolkit Examples
+ * %%
+ * Copyright (C) 2014 - 2015 Wikidata Toolkit Developers
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import org.wikidata.wdtk.datamodel.helpers.Datamodel;
+import org.wikidata.wdtk.datamodel.helpers.StatementBuilder;
+import org.wikidata.wdtk.datamodel.interfaces.*;
+import org.wikidata.wdtk.wikibaseapi.ApiConnection;
+import org.wikidata.wdtk.wikibaseapi.BasicApiConnection;
+import org.wikidata.wdtk.wikibaseapi.WikibaseDataEditor;
+import org.wikidata.wdtk.wikibaseapi.WikibaseDataFetcher;
+import org.wikidata.wdtk.wikibaseapi.apierrors.MediaWikiApiErrorException;
+
+import java.io.IOException;
+import java.util.Collections;
+
+/**
+ * This example shows how to create and modify media info data.
+ * <p>
+ * IMPORTANT: Running this program will perform edits on commons.wikimedia.beta.wmflabs.org.
+ * These edits are permanent and public. When running this program as is, the
+ * edits will be performed without logging in. This means that your current IP
+ * address will be recorded in the edit history of the page. If you prefer to
+ * use a login, please comment-in the respective line in the source code and
+ * modify it to use your credentials.
+ * <p>
+ *
+ * @author Thomas Pellissier Tanon
+ *
+ */
+public class EditOnlineMediaInfoExample {
+
+	/**
+	 * We use this to identify the site commons.wikimedia.beta.wmflabs.org. This IRI is not
+	 * essential for API interactions (the API knows of only one site and will
+	 * use local ids only), but it is important to use a fixed IRI in your code
+	 * for each site and not to mix IRIs.
+	 *
+	 * For wikimedia Commons, use {@link Datamodel.SITE_WIKIMEDIA_COMMONS}.
+	 */
+	private final static String siteIri = "https://commons.wikimedia.beta.wmflabs.org/entity/";
+
+	public static void main(String[] args) throws IOException, MediaWikiApiErrorException {
+		ExampleHelpers.configureLogging();
+		printDocumentation();
+
+		ApiConnection connection = new BasicApiConnection("https://commons.wikimedia.beta.wmflabs.org/w/api.php");
+		// Optional login -- required for operations on real wikis:
+		// connection.login("my username", "my password");
+		WikibaseDataFetcher wbdf = new WikibaseDataFetcher(connection, siteIri);
+		WikibaseDataEditor wbde = new WikibaseDataEditor(connection, siteIri);
+
+		System.out.println("*** Fetching the current media info or retrieving a dummy value...");
+		// If the entity does not exists, it's going to be returned anyway
+		MediaInfoDocument mediaInfoDocument = (MediaInfoDocument) wbdf.getEntityDocumentByTitle("commonswiki", "File:RandomImage 4658098723742867.jpg");
+
+		System.out.println("*** Editing a media info document ...");
+		mediaInfoDocument = mediaInfoDocument
+				.withLabel(Datamodel.makeMonolingualTextValue("random image", "en"))
+				.withStatement(StatementBuilder
+						.forSubjectAndProperty(mediaInfoDocument.getEntityId(), Datamodel.makeWikidataPropertyIdValue("P245962"))
+						.withValue(Datamodel.makeWikidataItemIdValue("Q81566")).build());
+		wbde.editMediaInfoDocument(mediaInfoDocument, false, "Wikidata Toolkit example media info edit", Collections.emptyList());
+	}
+
+	/**
+	 * Prints some basic documentation about this program.
+	 */
+	public static void printDocumentation() {
+		System.out.println("********************************************************************");
+		System.out.println("*** Wikidata Toolkit: MediaInfoDataExample");
+		System.out.println("*** ");
+		System.out.println("*** This program creates and modifies online data at commons.wikimedia.beta.wmflabs.org.");
+		System.out.println("*** It does not download any dump files. See source code for details.");
+		System.out.println("********************************************************************");
+	}
+}
diff --git a/wdtk-examples/src/main/java/org/wikidata/wdtk/examples/OnlineMediaInfoExample.java b/wdtk-examples/src/main/java/org/wikidata/wdtk/examples/OnlineMediaInfoExample.java
new file mode 100644
index 00000000..ce2ad3de
--- /dev/null
+++ b/wdtk-examples/src/main/java/org/wikidata/wdtk/examples/OnlineMediaInfoExample.java
@@ -0,0 +1,72 @@
+package org.wikidata.wdtk.examples;
+
+/*
+ * #%L
+ * Wikidata Toolkit Examples
+ * %%
+ * Copyright (C) 2014 - 2015 Wikidata Toolkit Developers
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import org.wikidata.wdtk.datamodel.interfaces.*;
+import org.wikidata.wdtk.wikibaseapi.WikibaseDataFetcher;
+import org.wikidata.wdtk.wikibaseapi.apierrors.MediaWikiApiErrorException;
+
+import java.io.IOException;
+
+/**
+ * This example shows how to retrieve MediaInfo data from the API.
+ *
+ * @author Thomas Pellissier Tanon
+ *
+ */
+public class OnlineMediaInfoExample {
+
+	public static void main(String[] args) throws IOException, MediaWikiApiErrorException {
+		ExampleHelpers.configureLogging();
+		printDocumentation();
+
+		WikibaseDataFetcher commonsDataFetcher = WikibaseDataFetcher.getWikimediaCommonsDataFetcher();
+		WikibaseDataFetcher wikidataDataFetcher = WikibaseDataFetcher.getWikidataDataFetcher();
+
+		System.out.println("*** Retrieving a media info document ...");
+		MediaInfoDocument mediaInfoDocument = (MediaInfoDocument) commonsDataFetcher.getEntityDocumentByTitle("commonswiki", "File:Black hole - Messier 87 crop max res.jpg");
+
+		// Print the English caption
+		System.out.println("Caption: " + mediaInfoDocument.getLabels().get("en").getText());
+
+		// Print the depict with labels from Wikidata:
+		for(Statement statement : mediaInfoDocument.findStatementGroup("P180").getStatements()) {
+			Value value = statement.getValue();
+			if(value instanceof ItemIdValue) {
+				ItemDocument depict = (ItemDocument) wikidataDataFetcher.getEntityDocument(((ItemIdValue) value).getId());
+				System.out.println("Depict: " + depict.getLabels().get("en").getText() + "(" + depict.getEntityId().getIri() + ")");
+			}
+		}
+
+		System.out.println("*** Done.");
+	}
+
+	/**
+	 * Prints some basic documentation about this program.
+	 */
+	public static void printDocumentation() {
+		System.out.println("********************************************************************");
+		System.out.println("*** Wikidata Toolkit: MediaInfoDataExample");
+		System.out.println("*** ");
+		System.out.println("*** It does not download any dump files. See source code for details.");
+		System.out.println("********************************************************************");
+	}
+}
diff --git a/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/ApiConnection.java b/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/ApiConnection.java
index 74d204b2..9900af42 100644
--- a/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/ApiConnection.java
+++ b/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/ApiConnection.java
@@ -70,6 +70,10 @@ public class ApiConnection {
 	 * URL of the API of test.wikidata.org.
 	 */
 	public final static String URL_TEST_WIKIDATA_API = "https://test.wikidata.org/w/api.php";
+	/**
+	 * URL of the API of commons.wikimedia.org.
+	 */
+	public final static String URL_WIKIMEDIA_COMMONS_API = "https://commons.wikimedia.org/w/api.php";
 
 	/**
 	 * Name of the HTTP parameter to submit an action to the API.
diff --git a/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/BasicApiConnection.java b/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/BasicApiConnection.java
index 60cbc2c0..c405db53 100644
--- a/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/BasicApiConnection.java
+++ b/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/BasicApiConnection.java
@@ -87,6 +87,15 @@ public class BasicApiConnection extends ApiConnection {
 	public static BasicApiConnection getTestWikidataApiConnection() {
 		return new BasicApiConnection(ApiConnection.URL_TEST_WIKIDATA_API);
 	}
+
+	/**
+	 * Creates an API connection to commons.wikimedia.org.
+	 *
+	 * @return {@link BasicApiConnection}
+	 */
+	public static BasicApiConnection getWikimediaCommonsApiConnection() {
+		return new BasicApiConnection(ApiConnection.URL_WIKIMEDIA_COMMONS_API);
+	}
 	
 	/**
 	 * Logs in using the specified user credentials. After successful login, the
diff --git a/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WbGetEntitiesAction.java b/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WbGetEntitiesAction.java
index 95ff47ee..30e08029 100644
--- a/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WbGetEntitiesAction.java
+++ b/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WbGetEntitiesAction.java
@@ -21,17 +21,17 @@ package org.wikidata.wdtk.wikibaseapi;
  */
 
 import java.io.IOException;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
+import java.util.*;
 import java.util.Map.Entry;
 
+import com.fasterxml.jackson.databind.DeserializationFeature;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.wikidata.wdtk.datamodel.helpers.Datamodel;
 import org.wikidata.wdtk.datamodel.helpers.DatamodelMapper;
 import org.wikidata.wdtk.datamodel.implementation.EntityDocumentImpl;
-import org.wikidata.wdtk.datamodel.implementation.ItemDocumentImpl;
-import org.wikidata.wdtk.datamodel.interfaces.EntityDocument;
+import org.wikidata.wdtk.datamodel.implementation.EntityIdValueImpl;
+import org.wikidata.wdtk.datamodel.interfaces.*;
 import org.wikidata.wdtk.wikibaseapi.apierrors.MediaWikiApiErrorException;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -157,6 +157,7 @@ public class WbGetEntitiesAction {
 
 		Map<String, String> parameters = new HashMap<>();
 		parameters.put(ApiConnection.PARAM_ACTION, "wbgetentities");
+		List<String> titlesList = titles == null ? Collections.emptyList() : Arrays.asList(titles.split("-"));
 
 		if (ids != null) {
 			parameters.put("ids", ids);
@@ -194,24 +195,28 @@ public class WbGetEntitiesAction {
 
 			JsonNode entities = root.path("entities");
 			Iterator<Entry<String,JsonNode>> entitiesIterator = entities.fields();
+			int i = 0;
 			while(entitiesIterator.hasNext()) {
 				Entry<String,JsonNode> entry = entitiesIterator.next();
 				JsonNode entityNode = entry.getValue();
-				if (!entityNode.has("missing")) {
+				if(!entityNode.has("missing")) {
 					try {
-						EntityDocument ed = mapper.treeToValue(entityNode, EntityDocumentImpl.class);
+						EntityDocument ed = mapper.reader()
+								.with(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)
+								.treeToValue(entityNode, EntityDocumentImpl.class);
 
 						if (titles == null) {
 							// We use the JSON key rather than the id of the value
 							// so that retrieving redirected entities works.
 							result.put(entry.getKey(), ed);
 						} else {
-							if (ed instanceof ItemDocumentImpl
-									&& ((ItemDocumentImpl) ed)
-											.getSiteLinks().containsKey(sites)) {
-								result.put(((ItemDocumentImpl) ed)
-										.getSiteLinks().get(sites)
-										.getPageTitle(), ed);
+							if (ed instanceof ItemDocument) {
+								SiteLink siteLink = ((ItemDocument) ed).getSiteLinks().get(sites);
+								if(siteLink != null) {
+									result.put(siteLink.getPageTitle(), ed);
+								}
+							} else if(ed instanceof MediaInfoDocument) {
+								result.put(entityNode.get("title").textValue(), ed);
 							}
 						}
 					} catch (JsonProcessingException e) {
@@ -219,7 +224,18 @@ public class WbGetEntitiesAction {
 								+ entityNode.path("id").asText("UNKNOWN")
 								+ ": " + e.toString());
 					}
+				} else if(entityNode.has("id")) {
+					try {
+						EntityIdValue entityIdValue = EntityIdValueImpl.fromId(entityNode.get("id").asText(), siteIri);
+						if(entityIdValue instanceof MediaInfoIdValue) {
+							//TODO: bad hack, it would be much nicer if the API would return the page title
+							result.put(titlesList.get(i), Datamodel.makeMediaInfoDocument((MediaInfoIdValue) entityIdValue));
+						}
+					} catch (IllegalArgumentException e) {
+						logger.warn("Invalid entity id returned: " + entityNode.get("id").asText());
+					}
 				}
+				i++;
 			}
 		} catch (IOException e) {
 			logger.error("Could not retrive data: " + e.toString());
diff --git a/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditor.java b/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditor.java
index 3138891a..4c8061cf 100644
--- a/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditor.java
+++ b/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditor.java
@@ -20,8 +20,6 @@ package org.wikidata.wdtk.wikibaseapi;
  * #L%
  */
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.wikidata.wdtk.datamodel.helpers.JsonSerializer;
 import org.wikidata.wdtk.datamodel.interfaces.*;
 import org.wikidata.wdtk.wikibaseapi.apierrors.MediaWikiApiErrorException;
@@ -38,9 +36,6 @@ import java.util.List;
  */
 public class WikibaseDataEditor {
 
-	static final Logger logger = LoggerFactory
-			.getLogger(WikibaseDataEditor.class);
-
 	/**
 	 * API Action to edit data.
 	 */
@@ -386,6 +381,56 @@ public class WikibaseDataEditor {
 				summary, tags);
 	}
 
+	/**
+	 * Writes the data for the given media info document with the summary message
+	 * as given. Optionally, the existing data is cleared (deleted).
+	 * It creates the media info if needed.
+	 * <p>
+	 * The id of the given media info document is used to specify which media info
+	 * document should be changed or created. The site IRI will be ignored for this.
+	 * <p>
+	 * The revision id of the given media info document is used to specify the
+	 * base revision, enabling the API to detect edit conflicts. The value 0 can
+	 * be used to omit this. It is strongly recommended to give a revision id
+	 * when making edits where the outcome depends on the previous state of the
+	 * data (i.e., any edit that does not use "clear").
+	 * <p>
+	 * If the data is not cleared, then the existing data will largely be
+	 * preserved. Statements with empty ids will be added without checking if
+	 * they exist already; statements with (valid) ids will replace any existing
+	 * statements with these ids or just be added if there are none. Labels
+	 * will be preserved for all languages for which no data is given at all.
+	 * For aliases this means that writing one alias in
+	 *
+	 * @param mediaInfoDocument
+	 *            the document that contains the data to be written
+	 * @param clear
+	 *            if true, the existing data will be replaced by the given data;
+	 *            if false, the given data will be added to the existing data,
+	 *            overwriting only parts that are set to new values
+	 * @param summary
+	 *            summary for the edit; will be prepended by an automatically
+	 *            generated comment; the length limit of the autocomment
+	 *            together with the summary is 260 characters: everything above
+	 *            that limit will be cut off
+	 * @param tags
+	 *            string identifiers of the tags to apply to the edit.
+	 * @return the modified media info document, or null if there was an error
+	 * @throws IOException
+	 *             if there was an IO problem, such as missing network
+	 *             connection
+	 */
+	public MediaInfoDocument editMediaInfoDocument(
+			MediaInfoDocument mediaInfoDocument, boolean clear, String summary,
+			List<String> tags)
+			throws IOException, MediaWikiApiErrorException {
+		String data = JsonSerializer.getJsonString(mediaInfoDocument);
+		return (MediaInfoDocument) this.wbEditingAction.wbEditEntity(
+				mediaInfoDocument.getEntityId().getId(), null, null, null,
+				data, clear, this.editAsBot, mediaInfoDocument.getRevisionId(),
+				summary, tags);
+	}
+
 	/**
 	 * Updates the statements of the item document identified by the given item
 	 * id. The updates are computed with respect to the current data found
diff --git a/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataFetcher.java b/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataFetcher.java
index 678296bc..4c613c88 100644
--- a/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataFetcher.java
+++ b/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataFetcher.java
@@ -1,7 +1,5 @@
 package org.wikidata.wdtk.wikibaseapi;
 
-import java.io.IOException;
-
 /*
  * #%L
  * Wikidata Toolkit Wikibase API
@@ -11,9 +9,9 @@ import java.io.IOException;
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *      http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -22,6 +20,7 @@ import java.io.IOException;
  * #L%
  */
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -29,15 +28,11 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.wikidata.wdtk.datamodel.helpers.Datamodel;
 import org.wikidata.wdtk.datamodel.interfaces.DocumentDataFilter;
 import org.wikidata.wdtk.datamodel.interfaces.EntityDocument;
 import org.wikidata.wdtk.wikibaseapi.apierrors.MediaWikiApiErrorException;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-
 /**
  * Simple class to fetch data from Wikibase via the online API.
  *
@@ -45,10 +40,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
  * @author Michael Guenther
  */
 public class WikibaseDataFetcher {
-
-	static final Logger logger = LoggerFactory
-			.getLogger(WikibaseDataFetcher.class);
-
 	/**
 	 * API Action to fetch data.
 	 */
@@ -61,11 +52,6 @@ public class WikibaseDataFetcher {
 	 */
 	final String siteIri;
 
-	/**
-	 * Mapper object used for deserializing JSON data.
-	 */
-	final ObjectMapper mapper = new ObjectMapper();
-
 	/**
 	 * Filter that is used to restrict API requests.
 	 */
@@ -86,10 +72,24 @@ public class WikibaseDataFetcher {
 	 */
 	public static WikibaseDataFetcher getWikidataDataFetcher() {
 		return new WikibaseDataFetcher(
-				ApiConnection.getWikidataApiConnection(),
+				BasicApiConnection.getWikidataApiConnection(),
 				Datamodel.SITE_WIKIDATA);
 	}
 
+
+	/**
+	 * Creates an object to fetch data from commons.wikimedia.org. This convenience
+	 * method creates a default {@link ApiConnection} that is not logged in. To
+	 * use an existing connection, the constructor
+	 * {@link #WikibaseDataFetcher(ApiConnection, String)} should be called,
+	 * using {@link Datamodel#SITE_WIKIMEDIA_COMMONS} as a site URI.
+	 */
+	public static WikibaseDataFetcher getWikimediaCommonsDataFetcher() {
+		return new WikibaseDataFetcher(
+				BasicApiConnection.getWikimediaCommonsApiConnection(),
+				Datamodel.SITE_WIKIMEDIA_COMMONS);
+	}
+
 	/**
 	 * Creates an object to fetch data from API with the given
 	 * {@link ApiConnection} object. The site URI is necessary since it is not
@@ -194,6 +194,9 @@ public class WikibaseDataFetcher {
 	 * Fetches the document for the entity that has a page of the given title on
 	 * the given site. Site keys should be some site identifier known to the
 	 * Wikibase site that is queried, such as "enwiki" for Wikidata.org.
+	 *
+	 * It could also be used to retrieve Wikimedia Commons MediaInfo entities
+	 * using the siteKey "commonswiki" and the file title (with the File: prefix) for title.
 	 * <p>
 	 * Note: This method will not work properly if a filter is set for sites
 	 * that excludes the requested site.


Test patch:
diff --git a/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoDocumentImplTest.java b/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoDocumentImplTest.java
index f6254171..d5d3ae8d 100644
--- a/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoDocumentImplTest.java
+++ b/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoDocumentImplTest.java
@@ -52,7 +52,7 @@ public class MediaInfoDocumentImplTest {
 	private final MediaInfoDocument mi1 = new MediaInfoDocumentImpl(mid, labelList, statementGroups,1234);
 	private final MediaInfoDocument mi2 = new MediaInfoDocumentImpl(mid, labelList, statementGroups,  1234);
 
-	private final String JSON_MEDIA_INFO_LABEL = "{\"type\":\"mediainfo\",\"id\":\"M42\",\"labels\":{\"en\":{\"language\":\"en\",\"value\":\"label\"}},\"statements\":{}}";
+	private final String JSON_MEDIA_INFO_LABEL = "{\"type\":\"mediainfo\",\"id\":\"M42\",\"labels\":{\"en\":{\"language\":\"en\",\"value\":\"label\"}},\"claims\":{}}";
 	private final String JSON_MEDIA_INFO_DESCRIPTION = "{\"type\":\"mediainfo\",\"id\":\"M42\",\"labels\":{},\"descriptions\":{},\"statements\":{}}";
 	private final String JSON_MEDIA_INFO_STATEMENTS = "{\"type\":\"mediainfo\",\"id\":\"M42\",\"labels\":{},\"statements\":{\"P42\":[{\"rank\":\"normal\",\"id\":\"MyId\",\"mainsnak\":{\"property\":\"P42\",\"snaktype\":\"somevalue\"},\"type\":\"statement\"}]}}";
 	private final String JSON_MEDIA_INFO_CLAIMS = "{\"type\":\"mediainfo\",\"id\":\"M42\",\"labels\":{},\"claims\":{\"P42\":[{\"rank\":\"normal\",\"id\":\"MyId\",\"mainsnak\":{\"property\":\"P42\",\"snaktype\":\"somevalue\"},\"type\":\"statement\"}]}}";
@@ -210,7 +210,7 @@ public class MediaInfoDocumentImplTest {
 	@Test
 	public void testStatementsToJson() throws JsonProcessingException {
 		MediaInfoDocumentImpl document = new MediaInfoDocumentImpl(mid, Collections.emptyList(), statementGroups,  0);
-		JsonComparator.compareJsonStrings(JSON_MEDIA_INFO_STATEMENTS, mapper.writeValueAsString(document));
+		JsonComparator.compareJsonStrings(JSON_MEDIA_INFO_CLAIMS, mapper.writeValueAsString(document));
 	}
 
 	@Test
diff --git a/wdtk-wikibaseapi/src/test/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditorTest.java b/wdtk-wikibaseapi/src/test/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditorTest.java
index 206e6de3..1ac91076 100644
--- a/wdtk-wikibaseapi/src/test/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditorTest.java
+++ b/wdtk-wikibaseapi/src/test/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditorTest.java
@@ -34,13 +34,7 @@ import org.wikidata.wdtk.datamodel.helpers.ItemDocumentBuilder;
 import org.wikidata.wdtk.datamodel.helpers.PropertyDocumentBuilder;
 import org.wikidata.wdtk.datamodel.helpers.StatementBuilder;
 import org.wikidata.wdtk.datamodel.helpers.JsonSerializer;
-import org.wikidata.wdtk.datamodel.interfaces.DatatypeIdValue;
-import org.wikidata.wdtk.datamodel.interfaces.ItemDocument;
-import org.wikidata.wdtk.datamodel.interfaces.ItemIdValue;
-import org.wikidata.wdtk.datamodel.interfaces.MonolingualTextValue;
-import org.wikidata.wdtk.datamodel.interfaces.PropertyDocument;
-import org.wikidata.wdtk.datamodel.interfaces.PropertyIdValue;
-import org.wikidata.wdtk.datamodel.interfaces.Statement;
+import org.wikidata.wdtk.datamodel.interfaces.*;
 import org.wikidata.wdtk.util.CompressionType;
 import org.wikidata.wdtk.wikibaseapi.apierrors.MediaWikiApiErrorException;
 import org.wikidata.wdtk.wikibaseapi.apierrors.TagsApplyNotAllowedException;
@@ -382,6 +376,40 @@ public class WikibaseDataEditorTest {
 
 		assertEquals(expectedResultDocument, result);
 	}
+
+	@Test
+	public void testEditMediaInfo() throws IOException, MediaWikiApiErrorException {
+		WikibaseDataEditor wde = new WikibaseDataEditor(this.con,
+				Datamodel.SITE_WIKIMEDIA_COMMONS);
+
+		MediaInfoIdValue id = Datamodel.makeWikimediaCommonsMediaInfoIdValue("M12223");
+
+		MediaInfoDocument mediaInfoDocument = Datamodel.makeMediaInfoDocument(id)
+			.withLabel(Datamodel.makeMonolingualTextValue("test", "en"));
+		MediaInfoDocument expectedResultDocument =  Datamodel.makeMediaInfoDocument(id)
+				.withLabel(Datamodel.makeMonolingualTextValue("test", "en"))
+				.withRevisionId(1235);
+		String resultData = JsonSerializer
+				.getJsonString(expectedResultDocument);
+		String expectedResult = "{\"entity\":" + resultData + ",\"success\":1}";
+
+		Map<String, String> params = new HashMap<>();
+		params.put("action", "wbeditentity");
+		params.put("id", "M12223");
+		params.put("summary", "My summary");
+		params.put("tags", "tag1|tag2");
+		params.put("token", "42307b93c79b0cb558d2dfb4c3c92e0955e06041+\\");
+		params.put("format", "json");
+		params.put("maxlag", "5");
+		String data = JsonSerializer.getJsonString(mediaInfoDocument);
+		params.put("data", data);
+		con.setWebResource(params, expectedResult);
+
+		MediaInfoDocument result = wde.editMediaInfoDocument(mediaInfoDocument, false,
+				"My summary", Arrays.asList("tag1", "tag2"));
+
+		assertEquals(expectedResultDocument, result);
+	}
 	
 	@Test
 	public void testStatementUpdateWithoutChanges() throws MediaWikiApiErrorException, IOException {
diff --git a/wdtk-wikibaseapi/src/test/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataFetcherTest.java b/wdtk-wikibaseapi/src/test/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataFetcherTest.java
index aa856f0f..5f9e45cf 100644
--- a/wdtk-wikibaseapi/src/test/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataFetcherTest.java
+++ b/wdtk-wikibaseapi/src/test/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataFetcherTest.java
@@ -145,6 +145,47 @@ public class WikibaseDataFetcherTest {
 		assertNull(result);
 	}
 
+	@Test
+	public void testWbGetMediaInfoEntityFromId() throws IOException, MediaWikiApiErrorException {
+		Map<String, String> parameters = new HashMap<>();
+		this.setStandardParameters(parameters);
+		parameters.put("ids", "M65057");
+		con.setWebResourceFromPath(parameters, getClass(),
+				"/wbgetentities-RandomImage.jpg.json", CompressionType.NONE);
+
+		EntityDocument result = wdf.getEntityDocument("M65057");
+
+		assertEquals("M65057", result.getEntityId().getId());
+	}
+
+	@Test
+	public void testWbGetMediaInfoEntityFromTitle() throws IOException, MediaWikiApiErrorException {
+		Map<String, String> parameters = new HashMap<>();
+		this.setStandardParameters(parameters);
+		parameters.put("titles", "File:RandomImage 4658098723742867.jpg");
+		parameters.put("sites", "commonswiki");
+		con.setWebResourceFromPath(parameters, getClass(),
+				"/wbgetentities-RandomImage.jpg.json", CompressionType.NONE);
+
+		EntityDocument result = wdf.getEntityDocumentByTitle("commonswiki", "File:RandomImage 4658098723742867.jpg");
+
+		assertEquals("M65057", result.getEntityId().getId());
+	}
+
+	@Test
+	public void testWbGetVirtualMediaInfoEntityFromTitle() throws IOException, MediaWikiApiErrorException {
+		Map<String, String> parameters = new HashMap<>();
+		this.setStandardParameters(parameters);
+		parameters.put("titles", "File:Test.jpg");
+		parameters.put("sites", "commonswiki");
+		con.setWebResourceFromPath(parameters, getClass(),
+				"/wbgetentities-virtual-Test.jpg.json", CompressionType.NONE);
+
+		EntityDocument result = wdf.getEntityDocumentByTitle("commonswiki", "File:Test.jpg");
+
+		assertEquals("M4215516", result.getEntityId().getId());
+	}
+
 	@Test
 	public void testWikidataDataFetcher() {
 		WikibaseDataFetcher wbdf = WikibaseDataFetcher.getWikidataDataFetcher();
diff --git a/wdtk-wikibaseapi/src/test/resources/wbgetentities-RandomImage.jpg.json b/wdtk-wikibaseapi/src/test/resources/wbgetentities-RandomImage.jpg.json
new file mode 100644
index 00000000..39b02e5a
--- /dev/null
+++ b/wdtk-wikibaseapi/src/test/resources/wbgetentities-RandomImage.jpg.json
@@ -0,0 +1,22 @@
+{
+  "entities": {
+    "M65057": {
+      "pageid": 65057,
+      "ns": 6,
+      "title": "File:RandomImage 4658098723742867.jpg",
+      "lastrevid": 146188,
+      "modified": "2019-11-19T20:36:58Z",
+      "type": "mediainfo",
+      "id": "M65057",
+      "labels": {
+        "en": {
+          "language": "en",
+          "value": "ddddd"
+        }
+      },
+      "descriptions": {},
+      "statements": []
+    }
+  },
+  "success": 1
+}
diff --git a/wdtk-wikibaseapi/src/test/resources/wbgetentities-virtual-Test.jpg.json b/wdtk-wikibaseapi/src/test/resources/wbgetentities-virtual-Test.jpg.json
new file mode 100644
index 00000000..297764e0
--- /dev/null
+++ b/wdtk-wikibaseapi/src/test/resources/wbgetentities-virtual-Test.jpg.json
@@ -0,0 +1,9 @@
+{
+  "entities": {
+    "M4215516": {
+      "id": "M4215516",
+      "missing": ""
+    }
+  },
+  "success": 1
+}