001    
002    package org.deepamehta.plugins.wikidata;
003    
004    import de.deepamehta.core.Association;
005    import de.deepamehta.core.AssociationType;
006    import de.deepamehta.core.RelatedAssociation;
007    import de.deepamehta.core.Topic;
008    import de.deepamehta.core.model.*;
009    import de.deepamehta.core.osgi.PluginActivator;
010    import de.deepamehta.core.service.ClientState;
011    import de.deepamehta.core.service.PluginService;
012    import de.deepamehta.core.service.annotation.ConsumesService;
013    import de.deepamehta.core.storage.spi.DeepaMehtaTransaction;
014    import de.deepamehta.plugins.accesscontrol.service.AccessControlService;
015    
016    import java.io.BufferedReader;
017    import java.io.IOException;
018    import java.io.InputStreamReader;
019    import java.net.HttpURLConnection;
020    import java.net.MalformedURLException;
021    import java.net.URL;
022    import java.util.HashMap;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.logging.Logger;
026    
027    import javax.ws.rs.*;
028    import javax.ws.rs.core.MediaType;
029    import javax.ws.rs.core.Response.Status;
030    
031    import org.codehaus.jettison.json.JSONArray;
032    import org.codehaus.jettison.json.JSONException;
033    import org.codehaus.jettison.json.JSONObject;
034    import org.deepamehta.plugins.wikidata.service.WikidataSearchService;
035    
036    
037    
038    /**
039     * A very basic plugin to search and explore wikidata.
040     * Allows to turn a \"Wikidata Search Result Entity\" (of type=property) into DeepaMehta 4 AssociationTypes.
041     *
042     * @author Malte Reißig (<malte@mikromedia.de>)
043     * @website https://github.com/mukil/dm4-wikidata
044     * @version 0.0.3-SNAPSHOT
045     */
046    
047    @Path("/wikidata")
048    @Consumes("application/json")
049    @Produces("application/json")
050    public class WikidataSearchPlugin extends PluginActivator implements WikidataSearchService {
051    
052        private Logger log = Logger.getLogger(getClass().getName());
053    
054        private final String DEEPAMEHTA_VERSION = "DeepaMehta 4.3";
055        private final String WIKIDATA_TYPE_SEARCH_VERSION = "0.0.3-SNAPSHOT";
056        private final String CHARSET = "UTF-8";
057    
058        // --- DeepaMehta 4 URIs
059    
060        private final String DM_WEBBROWSER_URL = "dm4.webbrowser.url";
061    
062        // --- Wikidata DeepaMehta URIs
063    
064        private final String WS_WIKIDATA_URI = "org.deepamehta.workspaces.wikidata";
065    
066        private final String WD_SEARCH_BUCKET_URI = "org.deepamehta.wikidata.search_bucket";
067        private final String WD_SEARCH_QUERY_URI = "org.deepamehta.wikidata.search_query";
068    
069        private final String WD_LANGUAGE_URI = "org.deepamehta.wikidata.language";
070        // private final String WD_LANGUAGE_NAME_URI = "org.deepamehta.wikidata.language_name";
071        // private final String WD_LANGUAGE_ISO_CODE_URI = "org.deepamehta.wikidata.language_code_iso";
072        private final String WD_LANGUAGE_DATA_URI_PREFIX = "org.deepamehta.wikidata.lang_";
073    
074        private final String WD_SEARCH_ENTITY_URI = "org.deepamehta.wikidata.search_entity";
075        private final String WD_SEARCH_ENTITY_LABEL_URI = "org.deepamehta.wikidata.search_entity_label";
076        private final String WD_SEARCH_ENTITY_TYPE_URI = "org.deepamehta.wikidata.search_entity_type";
077        private final String WD_SEARCH_ENTITY_ORDINAL_NR = "org.deepamehta.wikidata.search_ordinal_nr";
078        private final String WD_SEARCH_ENTITY_DESCR_URI = "org.deepamehta.wikidata.search_entity_description";
079        private final String WD_SEARCH_ENTITY_ALIAS_URI = "org.deepamehta.wikidata.search_entity_alias";
080        private final String WD_SEARCH_ENTITIY_DATA_URI_PREFIX = "org.deepamehta.wikidata.entity_";
081    
082        private final String WD_TEXT_TYPE_URI = "org.deepamehta.wikidata.text";
083        // private final String WD_COMMONS_MEDIA_TYPE_URI = "org.deepamehta.wikidata.commons_media";
084        // private final String WD_GLOBE_COORDINATE_TYPE_URI = "org.deepamehta.wikidata.globe_coordinate";
085    
086        private final String WD_ENTITY_CLAIM_EDGE = "org.deepamehta.wikidata.claim_edge";
087    
088        // --- Wikidata Service URIs
089    
090        private final String WD_SEARCH_ENTITIES_ENDPOINT =
091                "http://www.wikidata.org/w/api.php?action=wbsearchentities&format=json&limit=50";
092        private final String WD_CHECK_ENTITY_CLAIMS_ENDPOINT =
093                "http://www.wikidata.org/w/api.php?action=wbgetclaims&format=json"; // &ungroupedlist=0
094        private final String WD_GET_ENTITY_ENDPOINT = "http://www.wikidata.org/w/api.php?action=wbgetentities"
095                + "&props=info%7Csitelinks%2Furls%7Caliases%7Clabels%7Cdescriptions&dir=ascending&format=json";
096        private final String WD_SEARCH_ENTITY_TYPE_PROPERTY = "property";
097        private final String WD_SEARCH_ENTITY_TYPE_ITEM = "item";
098    
099        // --- Instance Variables
100    
101        private final String WIKIDATA_ENTITY_URL_PREFIX = "//www.wikidata.org/wiki/";
102        private final String WIKIDATA_PROPERTY_ENTITY_URL_PREFIX = "Property:";
103        private final String WIKIMEDIA_COMMONS_MEDIA_FILE_URL_PREFIX = "//commons.wikimedia.org/wiki/File:";
104    
105        private boolean isInitialized = false;
106        private AccessControlService acService = null;
107    
108    
109    
110        @GET
111        @Path("/search/{entity}/{query}/{language_code}")
112        @Produces(MediaType.APPLICATION_JSON)
113        @Override
114        public Topic searchWikidataEntity(@PathParam("query") String query, @PathParam("language_code") String lang,
115                    @HeaderParam("Cookie") ClientState clientState, @PathParam("entity") String type) {
116    
117            String json_result = "";
118            StringBuffer resultBody = new StringBuffer();
119            URL requestUri = null;
120            Topic search_bucket = null;
121            // sanity check (set en as default-language if nothing was provided by un-initialized language widget)
122            if (lang == null || lang.equals("undefined")) {
123                log.warning("Wikidata Language Search Option was not provided, now requesting data in EN");
124                lang = "en";
125            }
126            // start search operation
127            DeepaMehtaTransaction tx = dms.beginTx();
128            try {
129                // 1) fixme: Authorize request
130                requestUri = new URL(WD_SEARCH_ENTITIES_ENDPOINT + "&search="+ query +"&language="+ lang +"&type=" + type);
131                log.fine("Wikidata Search Entities Request: " + requestUri.toString());
132                // 2) initiate request
133                HttpURLConnection connection = (HttpURLConnection) requestUri.openConnection();
134                connection.setRequestMethod("GET");
135                connection.setRequestProperty("User-Agent", "DeepaMehta "+DEEPAMEHTA_VERSION+" - "
136                        + "Wikidata Search " + WIKIDATA_TYPE_SEARCH_VERSION);
137                // 3) check the response
138                int httpStatusCode = connection.getResponseCode();
139                if (httpStatusCode != HttpURLConnection.HTTP_OK) {
140                    throw new WebApplicationException(new Throwable("Error with HTTPConnection."),
141                            Status.INTERNAL_SERVER_ERROR);
142                }
143                // 4) read in the response
144                BufferedReader rd = new BufferedReader(new InputStreamReader(connection.getInputStream(), CHARSET));
145                for (String input; (input = rd.readLine()) != null;) {
146                    resultBody.append(input);
147                }
148                rd.close();
149                // 5) process response
150                if (resultBody.toString().isEmpty()) {
151                    throw new WebApplicationException(new RuntimeException("Wikidata was silent."),
152                            Status.NO_CONTENT);
153                } else {
154                    // ..) Create Wikidata Search Bucket
155                    CompositeValueModel bucket_model = new CompositeValueModel();
156                    bucket_model.put(WD_SEARCH_QUERY_URI, query);
157                    bucket_model.putRef(WD_LANGUAGE_URI, WD_LANGUAGE_DATA_URI_PREFIX + lang);
158                    json_result = resultBody.toString();
159                    log.fine("Wikidata Search Request Response: " + json_result);
160                    processWikidataEntitySearch(json_result, bucket_model, type, lang);
161                    search_bucket = dms.createTopic(new TopicModel(WD_SEARCH_BUCKET_URI, bucket_model), clientState);
162                    // workaround: addRef does not (yet) fetchComposite, so fetchComposite=true
163                    search_bucket = dms.getTopic(search_bucket.getId(), true);
164                    log.info("Wikidata Search Bucket for "+ query +" in ("+ lang +") was CREATED");
165                }
166                tx.success();
167            } catch (MalformedURLException e) {
168                log.warning("Wikidata Plugin: MalformedURLException ..." + e.getMessage());
169                tx.failure();
170                throw new RuntimeException("Could not find wikidata endpoint.", e);
171            } catch (IOException ioe) {
172                tx.failure();
173                throw new WebApplicationException(new Throwable(ioe), Status.BAD_REQUEST);
174            } catch (Exception e) {
175                tx.failure();
176                throw new WebApplicationException(new Throwable(e), Status.INTERNAL_SERVER_ERROR);
177            } finally {
178                tx.finish();
179                return search_bucket;
180            }
181        }
182    
183        @GET
184        @Path("/{entityId}/{language_code}")
185        @Produces(MediaType.APPLICATION_JSON)
186        @Override
187        public Topic getOrCreateWikidataEntity(@PathParam("entityId") String entityId,
188            @PathParam("language_code") String language_code, @HeaderParam("Cookie") ClientState clientState) {
189            String json_result = "";
190            StringBuffer resultBody = new StringBuffer();
191            URL requestUri = null;
192            Topic entity = null;
193            // sanity check (set en as default-language if nothing was provided by un-initialized language widget)
194            if (language_code == null || language_code.equals("undefined")) {
195                log.warning("Wikidata Language Search Option was not provided, now requesting data in EN");
196                language_code = "en";
197            }
198            try {
199                // 1) fixme: Authorize request
200                // &sites=dewiki&&languages=de
201                requestUri = new URL(WD_GET_ENTITY_ENDPOINT + "&ids="+ entityId + "&languages=" + language_code);
202                log.fine("Requesting Wikidata Entity Details " + requestUri.toString());
203                // 2) initiate request
204                HttpURLConnection connection = (HttpURLConnection) requestUri.openConnection();
205                connection.setRequestMethod("GET");
206                connection.setRequestProperty("User-Agent", "DeepaMehta "+DEEPAMEHTA_VERSION+" - "
207                        + "Wikidata Search " + WIKIDATA_TYPE_SEARCH_VERSION);
208                // 3) check the response
209                int httpStatusCode = connection.getResponseCode();
210                if (httpStatusCode != HttpURLConnection.HTTP_OK) {
211                    throw new WebApplicationException(new Throwable("Error with HTTPConnection."),
212                            Status.INTERNAL_SERVER_ERROR);
213                }
214                // 4) read in the response
215                BufferedReader rd = new BufferedReader(new InputStreamReader(connection.getInputStream(), CHARSET));
216                for (String input; (input = rd.readLine()) != null;) {
217                    resultBody.append(input);
218                }
219                rd.close();
220                // 5) process response
221                if (resultBody.toString().isEmpty()) {
222                    throw new WebApplicationException(new RuntimeException("Wikidata was silent."),
223                            Status.NO_CONTENT);
224                } else {
225                    // 6) Create or Update Wikidata Search Entity
226                    json_result = resultBody.toString();
227                    log.fine("Wikidata Entity Request Response: " + json_result);
228                    JSONObject response = new JSONObject(json_result);
229                    JSONObject entities = response.getJSONObject("entities");
230                    JSONObject response_entity = entities.getJSONObject(entityId);
231                    // 0) Check if we need to CREATE or UPDATE our search result entity item
232                    Topic existingEntity = dms.getTopic("uri",
233                                new SimpleValue(WD_SEARCH_ENTITIY_DATA_URI_PREFIX + entityId), true);
234                    if (existingEntity == null) {
235                        entity = createWikidataSearchEntity(response_entity, language_code, clientState);
236                    } else {
237                        // Updates labels, descriptions, aliases, url and (query) language
238                        entity = updateWikidataEntity(existingEntity, response_entity, language_code, clientState);
239                    }
240                }
241            } catch (MalformedURLException e) {
242                log.warning("Wikidata Plugin: MalformedURLException ..." + e.getMessage());
243                throw new RuntimeException("Could not find wikidata endpoint.", e);
244            } catch (IOException ioe) {
245                throw new WebApplicationException(new Throwable(ioe), Status.BAD_REQUEST);
246            } catch (JSONException je) {
247                throw new WebApplicationException(new Throwable(je), Status.INTERNAL_SERVER_ERROR);
248            } catch (Exception e) {
249                throw new WebApplicationException(new Throwable(e), Status.INTERNAL_SERVER_ERROR);
250            } finally {
251                return entity;
252            }
253        }
254    
255        @GET
256        @Path("/check/claims/{id}/{language_code}")
257        @Produces(MediaType.APPLICATION_JSON)
258        @Override
259        public Topic loadClaimsAndRelatedWikidataItems(@PathParam("id") long topicId,
260                @PathParam("language_code") String language_option, @HeaderParam("Cookie") ClientState clientState) {
261    
262            String json_result = "";
263            StringBuffer resultBody = new StringBuffer();
264            URL requestUri = null;
265            Topic wikidataItem = dms.getTopic(topicId, true);
266            // 0) sanity check (set en as default-language if nothing was provided by un-initialized language widget)
267            if (language_option == null || language_option.equals("undefined")) {
268                log.warning("Wikidata Language Search Option was not provided, now requesting data in EN.");
269                language_option = "en";
270            }
271            String wikidataId = wikidataItem.getUri().replaceAll(WD_SEARCH_ENTITIY_DATA_URI_PREFIX, "");
272            DeepaMehtaTransaction tx = dms.beginTx();
273            try {
274                // 1) ### Authorize request
275                // 2) ### be explicit and add "&rank=normal" to wbgetclaims-call
276                requestUri = new URL(WD_CHECK_ENTITY_CLAIMS_ENDPOINT + "&entity=" + wikidataId);
277                log.fine("Requesting Wikidata Entity Claims: " + requestUri.toString());
278                // 2) initiate request
279                HttpURLConnection connection = (HttpURLConnection) requestUri.openConnection();
280                connection.setRequestMethod("GET");
281                connection.setRequestProperty("User-Agent", "DeepaMehta "+DEEPAMEHTA_VERSION+" - "
282                        + "Wikidata Search " + WIKIDATA_TYPE_SEARCH_VERSION);
283                // 3) check the response
284                int httpStatusCode = connection.getResponseCode();
285                if (httpStatusCode != HttpURLConnection.HTTP_OK) {
286                    throw new WebApplicationException(new Throwable("Error with HTTPConnection."),
287                            Status.INTERNAL_SERVER_ERROR);
288                }
289                // 4) read in the response
290                BufferedReader rd = new BufferedReader(new InputStreamReader(connection.getInputStream(), CHARSET));
291                for (String input; (input = rd.readLine()) != null;) {
292                    resultBody.append(input);
293                }
294                rd.close();
295                // 5) process response
296                if (resultBody.toString().isEmpty()) {
297                    throw new WebApplicationException(new RuntimeException("Wikidata was silent."),
298                            Status.NO_CONTENT);
299                } else {
300                    json_result = resultBody.toString();
301                    log.fine("Wikidata Claim Request Response: " + json_result);
302                    processWikidataClaims(json_result, wikidataItem, language_option, clientState);
303                    log.info("Wikidata Claim Response is FINE");
304                }
305                tx.success();
306            } catch (MalformedURLException e) {
307                log.warning("Wikidata Plugin: MalformedURLException ..." + e.getMessage());
308                tx.failure();
309                throw new RuntimeException("Could not find wikidata endpoint.", e);
310            } catch (IOException ioe) {
311                tx.failure();
312                throw new WebApplicationException(new Throwable(ioe), Status.BAD_REQUEST);
313            } catch (Exception e) {
314                tx.failure();
315                throw new WebApplicationException(new Throwable(e), Status.INTERNAL_SERVER_ERROR);
316            } finally {
317                tx.finish();
318                return wikidataItem;
319            }
320        }
321    
322        @GET
323        @Path("/property/turn/{id}")
324        @Produces(MediaType.APPLICATION_JSON)
325        @Override
326        public Topic createWikidataAssociationType(@PathParam("id") long id,
327                @HeaderParam("Cookie") ClientState clientState) {
328    
329            AssociationType association_type = null;
330            DeepaMehtaTransaction tx = dms.beginTx();
331            try {
332                Topic property_entity = dms.getTopic(id, true);
333                // 1) Create new Association Type model
334                String property_name = property_entity.getSimpleValue().toString();
335                AssociationTypeModel assoc_type_model = new AssociationTypeModel("org.deepamehta.wikidata.assoctype_"
336                        + property_entity.getUri().replaceAll(WD_SEARCH_ENTITIY_DATA_URI_PREFIX, ""),
337                        property_name, "dm4.core.text");
338                association_type = dms.createAssociationType(assoc_type_model, clientState);
339                // 2) Assign to "Wikidata" Workspace
340                assignToWikidataWorkspace(association_type);
341                // 3) Associated search-result-entity to new assoc-type (to keep track)
342                dms.createAssociation(new AssociationModel("dm4.core.association",
343                        new TopicRoleModel(property_entity.getUri(), "dm4.core.default"),
344                        new TopicRoleModel(association_type.getUri(), "dm4.core.default")
345                        ), clientState);
346                log.info("Turned wikidata property \""+ property_entity.getUri() +"\" into DM Association Type!");
347                tx.success();
348            } catch (Error e) {
349                tx.failure();
350                log.warning("OH: The Wikidata Plugin experienced an unforeseen error! "+ e.getMessage());
351            } finally {
352                tx.finish();
353                return association_type;
354            }
355        }
356    
357        @GET
358        @Path("/property/related/claims/{id}")
359        @Produces(MediaType.APPLICATION_JSON)
360        @Override
361        public List<RelatedAssociation> getTopicRelatedAssociations (@PathParam("id") long topicId) {
362            Topic topic = dms.getTopic(topicId, false);
363            List<RelatedAssociation> associations = topic.getRelatedAssociations("dm4.core.aggregation",
364                    "dm4.core.child", "dm4.core.parent", "org.deepamehta.wikidata.claim_edge", false, false);
365            return associations;
366        }
367    
368    
369        // --
370        // ---  Wikidata Search (Application Specific) Private Methods
371        // --
372    
373        private void processWikidataEntitySearch(String json_result, CompositeValueModel search_bucket,
374                String type, String lang) {
375            try {
376                JSONObject response = new JSONObject(json_result);
377                JSONArray result = response.getJSONArray("search");
378                if (result.length() > 0) {
379                    for (int i = 0; i < result.length(); i++) {
380                        JSONObject entity_response = result.getJSONObject(i);
381                        // Check if entity already exists
382                        String id = entity_response.getString("id");
383                        Topic existing_entity = dms.getTopic("uri",
384                                new SimpleValue(WD_SEARCH_ENTITIY_DATA_URI_PREFIX + id), false);
385                        if (existing_entity == null) {
386                            // Create new search entity composite
387                            String name = entity_response.getString("label");
388                            String url = entity_response.getString("url");
389                            //
390                            CompositeValueModel entity_composite = new CompositeValueModel();
391                            entity_composite.put(WD_SEARCH_ENTITY_LABEL_URI, name);
392                            if (entity_response.has("description")) {
393                                String description = entity_response.getString("description");
394                                entity_composite.put(WD_SEARCH_ENTITY_DESCR_URI, description);
395                            }
396                            entity_composite.put(DM_WEBBROWSER_URL, url);
397                            if (entity_response.has("aliases")) {
398                                JSONArray aliases = entity_response.getJSONArray("aliases");
399                                for (int a=0; a < aliases.length(); a++) {
400                                    String alias = aliases.getString(a);
401                                    entity_composite.add(WD_SEARCH_ENTITY_ALIAS_URI,
402                                        new TopicModel(WD_SEARCH_ENTITY_ALIAS_URI, new SimpleValue(alias)));
403                                }
404                            }
405                            // set enity place in resultset
406                            entity_composite.put(WD_SEARCH_ENTITY_ORDINAL_NR, i);
407                            // set entity-type
408                            entity_composite.put(WD_SEARCH_ENTITY_TYPE_URI, type);
409                            // set language-value on entity-result
410                            entity_composite.putRef(WD_LANGUAGE_URI, WD_LANGUAGE_DATA_URI_PREFIX + lang);
411                            TopicModel entity_model = new TopicModel(WD_SEARCH_ENTITIY_DATA_URI_PREFIX + id,
412                                    WD_SEARCH_ENTITY_URI, entity_composite);
413                            // create and reference  entity in wikidata search bucket
414                            search_bucket.add(WD_SEARCH_ENTITY_URI, entity_model);
415                        } else {
416                            // reference existing entity in wikidata search bucket by URI
417                            search_bucket.addRef(WD_SEARCH_ENTITY_URI, WD_SEARCH_ENTITIY_DATA_URI_PREFIX + id);
418                        }
419                    }
420                }
421            } catch (JSONException ex) {
422                log.warning("Wikidata Plugin: JSONException during processing a wikidata entity search response. "
423                        + ex.getMessage());
424            }
425        }
426    
427        private Topic createWikidataSearchEntity(JSONObject entity_response, String lang, ClientState clientState) {
428            Topic entity = null;
429            DeepaMehtaTransaction tx = dms.beginTx();
430            try {
431                String id = entity_response.getString("id");
432                // Create new search entity composite
433                CompositeValueModel entity_composite = buildWikidataEntityModel(entity_response, lang);
434                TopicModel entity_model = new TopicModel(WD_SEARCH_ENTITIY_DATA_URI_PREFIX + id,
435                        WD_SEARCH_ENTITY_URI, entity_composite);
436                entity = dms.createTopic(entity_model, clientState);
437                log.fine("Wikidata Search Entity Created (" +
438                    entity_composite.getString(WD_SEARCH_ENTITY_TYPE_URI)+ "): \"" + entity.getSimpleValue() +"\" - FINE!");
439                tx.success();
440            } catch (Exception ex) {
441                log.warning("FAILED to create a \"Wikidata Search Entity\" caused by " + ex.getMessage());
442                tx.failure();
443            } finally {
444                tx.finish();
445                return entity;
446            }
447        }
448    
449        private Topic updateWikidataEntity(Topic entity, JSONObject entity_response, String lang, ClientState clientState) {
450            DeepaMehtaTransaction tx = dms.beginTx();
451            try {
452                // Update existing search entity topic
453                CompositeValueModel entity_composite = buildWikidataEntityModel(entity_response, lang);
454                TopicModel entity_model = new TopicModel(entity.getId(), entity_composite);
455                dms.updateTopic(entity_model, clientState);
456                log.fine("Wikidata Search Entity Updated (" +
457                    entity_composite.getString(WD_SEARCH_ENTITY_TYPE_URI)+ "): \"" + entity.getSimpleValue() +"\" - FINE!");
458                tx.success();
459            } catch (Exception ex) {
460                log.warning("FAILED to UPDATE \"Wikidata Search Entity\" caused by " + ex.getMessage());
461                tx.failure();
462            } finally {
463                tx.finish();
464                return entity;
465            }
466        }
467    
468        private CompositeValueModel buildWikidataEntityModel(JSONObject entity_response, String lang) {
469            CompositeValueModel entity_composite = new CompositeValueModel();
470            try {
471                String id = entity_response.getString("id");
472                String type = entity_response.getString("type");
473                entity_composite = new CompositeValueModel();
474                // main label
475                if (entity_response.has("labels")) {
476                    JSONObject labels = entity_response.getJSONObject("labels");
477                    JSONObject languaged_label = labels.getJSONObject(lang);
478                    String label = languaged_label.getString("value");
479                    entity_composite.put(WD_SEARCH_ENTITY_LABEL_URI, label);
480                }
481                // main description
482                if (entity_response.has("descriptions")) {
483                    JSONObject descriptions = entity_response.getJSONObject("descriptions");
484                    JSONObject languaged_descr = descriptions.getJSONObject(lang);
485                    String description = languaged_descr.getString("value");
486                    entity_composite.put(WD_SEARCH_ENTITY_DESCR_URI, description);
487                }
488                // aliases
489                if (entity_response.has("aliases")) {
490                    JSONObject aliases = entity_response.getJSONObject("aliases");
491                    JSONArray languaged_aliases = aliases.getJSONArray(lang);
492                    for (int a=0; a < languaged_aliases.length(); a++) {
493                        JSONObject alias_object = languaged_aliases.getJSONObject(a);
494                        String alias = alias_object.getString("value");
495                        entity_composite.add(WD_SEARCH_ENTITY_ALIAS_URI,
496                            new TopicModel(WD_SEARCH_ENTITY_ALIAS_URI, new SimpleValue(alias)));
497                    }
498                }
499                // set wikidata url
500                if (type.equals(WD_SEARCH_ENTITY_TYPE_PROPERTY)) {
501                    entity_composite.put(DM_WEBBROWSER_URL, WIKIDATA_ENTITY_URL_PREFIX
502                            + WIKIDATA_PROPERTY_ENTITY_URL_PREFIX + id);
503                } else {
504                    entity_composite.put(DM_WEBBROWSER_URL, WIKIDATA_ENTITY_URL_PREFIX + id);
505                }
506                // set language-value on entity-result
507                entity_composite.putRef(WD_LANGUAGE_URI, WD_LANGUAGE_DATA_URI_PREFIX + lang);
508                // ### sitelinks
509                /** if (entity_response.has("sitelinks")) {
510                    JSONObject sitelinks = entity_response.getJSONObject("sitelinks");
511                    if (sitelinks.has(lang + "wiki")) {
512                        JSONObject sitelink = sitelinks.getJSONObject(lang + "wiki");
513                        entity_composite.put(DM_WEBBROWSER_URL, sitelink.getString("url"));
514                    } else {
515                        log.warning("There is no sitelink for this item in this language/wiki: " + lang + "wiki");
516                    }
517                } **/
518                entity_composite.put(WD_SEARCH_ENTITY_TYPE_URI, type);
519            } catch (JSONException jex) {
520                log.warning("JSONException during build up of the search-entities composite model");
521                return null;
522            } finally {
523                return entity_composite;
524            }
525        }
526    
527    
528        private void processWikidataClaims(String json_result, Topic wikidataItem, String language_code,
529                ClientState clientState) {
530            try {
531                JSONObject response = new JSONObject(json_result);
532                JSONObject result = response.getJSONObject("claims");
533                // ### Needs to identify if claims (already imported in DM4) are not yet part of the current wikidata-data
534                Iterator properties = result.keys();
535                log.info("Wikidata Plugin is processing all properties part of related CLAIMS");
536                Topic propertyEntity = null;
537                while (properties.hasNext()) {
538                    String property_id = properties.next().toString();
539                    // 1) Load related property-entity
540                    propertyEntity = getOrCreateWikidataEntity(property_id, language_code, clientState);
541                    // HashMap<String, List<Topic>> all_entities = new HashMap<String, List<Topic>>();
542                    JSONArray property_listing = result.getJSONArray(property_id);
543                    for (int i=0; i < property_listing.length(); i++) {
544                        // 2) fetch related wikidata entity
545                        Topic referencedItemEntity = null;
546                        JSONObject entity_response = property_listing.getJSONObject(i);
547                        JSONObject mainsnak = entity_response.getJSONObject("mainsnak");
548                        String claim_guid = entity_response.getString("id");
549                        // 3) build up item as part of the claim (if so)
550                        String itemId = "";
551                        String snakDataType = mainsnak.getString("datatype");
552                        JSONObject snakDataValue = mainsnak.getJSONObject("datavalue");
553                        // ..) depending on the various (claimed/realted) value-types
554                        if (snakDataType.equals("wikibase-item")) {
555                            // log.info("Wikibase Item claimed via \"" + propertyEntity.getSimpleValue() + "\"");
556                            JSONObject snakDataValueValue = snakDataValue.getJSONObject("value");
557                            long numericId = snakDataValueValue.getLong("numeric-id");
558                            itemId = "Q" + numericId; // is this always of entity-type "item"? responses looks like.
559                            referencedItemEntity = getOrCreateWikidataEntity(itemId, language_code, clientState);
560                        } else if (snakDataType.equals("commonsMedia")) {
561                            // do relate wikidata.commons_media
562                            log.fine("Commons Media claimed via \"" + propertyEntity.getSimpleValue()
563                                    + "\" ("+language_code+") DEBUG:");
564                            log.fine("  " + snakDataValue.toString());
565                            // ### make use of WIKIMEDIA_COMMONS_MEDIA_FILE_URL_PREFIX and implement page-renderer
566                        } else if (snakDataType.equals("globe-coordinate")) {
567                            // do relate wikidata.globe_coordinate
568                            log.fine("Globe Coordinate claimed via \"" + propertyEntity.getSimpleValue()
569                                    + "\" ("+language_code+") DEBUG:");
570                            log.fine("  " + snakDataValue.toString());
571                        } else if (snakDataType.equals("string")) {
572                            if (snakDataValue.has("value")) {
573                                String value = snakDataValue.getString("value");
574                                referencedItemEntity = getOrCreateWikidataText(value, language_code, clientState);
575                            } else {
576                                log.warning("Could not access wikidata-text value - json-response EMPTY!");
577                            }
578                        } else {
579                            log.warning("Value claimed as " + propertyEntity.getSimpleValue() + " is not of any known type"
580                                    + " wikibase-item but \"" + snakDataType +"\" ("+snakDataValue+")");
581                            // e.g. snakDataType.equals("quantity")
582                        }
583                        // store topic reference to (new or already existing) wikidata-entity/ resp. -value topic
584                        if (referencedItemEntity != null) {
585                            createWikidataClaimEdge(claim_guid, wikidataItem, referencedItemEntity,
586                                propertyEntity, clientState);
587                        } else {
588                            log.warning("SKIPPED creating claim of type \""+snakDataType+"\" value for "
589                                    + "\""+propertyEntity.getSimpleValue()+"\"");
590                        }
591                    }
592                    /** Iterator entity_iterator = all_entities.keySet().iterator();
593                    StringBuffer requesting_ids = new StringBuffer();
594                    while (entity_iterator.hasNext()) {
595                        String entity_id = entity_iterator.next().toString();
596                        requesting_ids.append(entity_id + "|");
597                    }
598                    log.info("Requesting ALL ITEMS for " +property_id+ ": " + requesting_ids.toString());
599                    omitting this solution bcause: "*": "Too many values supplied for parameter 'ids': the limit is 50" **/
600                }
601            } catch (JSONException ex) {
602                log.warning("JSONException during processing a wikidata claim. " + ex.getMessage());
603            }
604        }
605    
606        private Association createWikidataClaimEdge (String claim_guid, Topic one, Topic two,
607                Topic property, ClientState clientState) {
608            DeepaMehtaTransaction tx = dms.beginTx();
609            Association claim = null;
610            try {
611                if (!associationExists(WD_ENTITY_CLAIM_EDGE, one, two)) {
612                    // 1) Create \"Wikidata Claim\"-Edge with GUID
613                    claim = dms.createAssociation(new AssociationModel(WD_ENTITY_CLAIM_EDGE,
614                        new TopicRoleModel(one.getId(), "dm4.core.default"),
615                        new TopicRoleModel(two.getId(), "dm4.core.default")), clientState);
616                    claim.setUri(claim_guid);
617                    log.info("Created \"Wikidata Claim\" with GUID: " + claim.getUri() +" for \"" + two.getSimpleValue() +
618                                    " (property: " + property.getSimpleValue() +
619                                    "\") for \"" + one.getSimpleValue() + "\" - FINE");
620                    // 2) Assign wikidata property (=Wikidata Search Entity) to this claim-edge
621                    claim.setCompositeValue(new CompositeValueModel().putRef(WD_SEARCH_ENTITY_URI,
622                            property.getUri()), clientState, null);
623                }
624                tx.success();
625            } catch (Exception e) {
626                log.severe("FAILED to create a \"Claim\" between \""+one.getSimpleValue()+"\" - \""+two.getSimpleValue());
627                tx.failure();
628                throw new RuntimeException(e);
629            } finally {
630                tx.finish();
631                return claim;
632            }
633        }
634    
635        private Topic getOrCreateWikidataText(String value, String lang, ClientState clientState) {
636            Topic textValue = null;
637            // 1) query for text-value
638            log.info("Value: " + value);
639            try {
640                textValue = dms.getTopic(WD_TEXT_TYPE_URI, new SimpleValue(value), false);
641            } catch (Exception ex) {
642                log.info("Could not find a wikidata-text value topic for " + value + ex.getMessage());
643            }
644            // 2) re-use  or create
645            DeepaMehtaTransaction tx = dms.beginTx();
646            try {
647                if (textValue == null) {
648                    textValue = dms.createTopic(new TopicModel(WD_TEXT_TYPE_URI, new SimpleValue(value)), clientState);
649                    log.info("CREATED \"Wikidata Text\" - \"" + value +"\" (" + lang + ") - OK!");
650                } else {
651                    log.info("FETCHED \"Wikidata Text\" - \"" + textValue.getSimpleValue() +"\" "
652                            + "(" + lang + ") - Re-using it!");
653                }
654                tx.success();
655            } catch (Exception ex) {
656                log.warning("FAILURE during creating a wikidata value topic: " + ex.getLocalizedMessage());
657                tx.failure();
658            } finally {
659                tx.finish();
660                return textValue;
661            }
662        }
663    
664    
665    
666        // --
667        // --- DeepaMehta 4 Plugin Related Private Methods
668        // --
669    
670        private boolean associationExists(String edge_type, Topic item, Topic user) {
671            List<Association> results = dms.getAssociations(item.getId(), user.getId(), edge_type);
672            return (results.size() > 0) ? true : false;
673        }
674    
675        private void assignToWikidataWorkspace(Topic topic) {
676            // fixme: remove assignment of type to other (selected via clientState) workspace
677            Topic defaultWorkspace = dms.getTopic("uri", new SimpleValue(WS_WIKIDATA_URI), false);
678            dms.createAssociation(new AssociationModel("dm4.core.aggregation",
679                new TopicRoleModel(topic.getId(), "dm4.core.parent"),
680                new TopicRoleModel(defaultWorkspace.getId(), "dm4.core.child")
681            ), null);
682        }
683    
684        /** --- Implementing PluginService Interfaces to consume AccessControlService --- */
685    
686        @Override
687        @ConsumesService({
688            "de.deepamehta.plugins.accesscontrol.service.AccessControlService"
689        })
690        public void serviceArrived(PluginService service) {
691            if (service instanceof AccessControlService) {
692                acService = (AccessControlService) service;
693            }
694        }
695    
696        @Override
697        @ConsumesService({
698            "de.deepamehta.plugins.accesscontrol.service.AccessControlService"
699        })
700        public void serviceGone(PluginService service) {
701            if (service == acService) {
702                acService = null;
703            }
704        }
705    
706    }