From 63cf416436c03f86223f546c45aab7e85797fc43 Mon Sep 17 00:00:00 2001 From: Fabian Steeg Date: Thu, 17 Aug 2017 15:10:32 +0200 Subject: [PATCH] Set up detail pages for authorities See https://github.com/hbz/lobid-authorities/issues/14 --- app/controllers/HomeController.java | 28 ++- app/models/AuthorityResource.java | 211 +++++++++++++++++++++++ app/views/api.scala.html | 2 +- app/views/details.scala.html | 29 ++++ app/views/search.scala.html | 2 +- conf/application.conf | 1 + conf/routes | 4 +- public/images/json-ld.png | Bin 0 -> 2591 bytes test/controllers/HomeControllerTest.java | 8 +- test/models/ModelTest.java | 32 ++++ 10 files changed, 303 insertions(+), 14 deletions(-) create mode 100644 app/models/AuthorityResource.java create mode 100644 app/views/details.scala.html create mode 100644 public/images/json-ld.png create mode 100644 test/models/ModelTest.java diff --git a/app/controllers/HomeController.java b/app/controllers/HomeController.java index 1216746..b4461e2 100644 --- a/app/controllers/HomeController.java +++ b/app/controllers/HomeController.java @@ -37,6 +37,7 @@ import com.typesafe.config.ConfigObject; import apps.Convert; +import models.AuthorityResource; import modules.IndexComponent; import play.Environment; import play.Logger; @@ -88,21 +89,33 @@ public Result api() { controllers.routes.HomeController.search("type:CorporateBody", 0, 10, format).toString(), // "Pagination", controllers.routes.HomeController.search("london", 50, 100, format).toString()); ImmutableMap getSamples = ImmutableMap.of(// - "London", controllers.routes.HomeController.authority("4074335-4").toString(), // - "hbz", controllers.routes.HomeController.authority("2047974-8").toString(), // - "Goethe", controllers.routes.HomeController.authority("118540238").toString()); + "London", controllers.routes.HomeController.authorityJson("4074335-4").toString(), // + "hbz", controllers.routes.HomeController.authorityJson("2047974-8").toString(), // + "Goethe", controllers.routes.HomeController.authorityJson("118540238").toString()); return ok(views.html.api.render(searchSamples, getSamples)); } - public Result authority(String id) { + public Result authorityJson(String id) { + String jsonLd = getAuthorityResource(id); + return jsonLd == null ? gnd(id) : ok(prettyJsonString(Json.parse(jsonLd))).as(config("index.content")); + } + + public Result authorityHtml(String id) { + String jsonLd = getAuthorityResource(id); + JsonNode json = Json.parse(jsonLd); + AuthorityResource entity = Json.fromJson(json, AuthorityResource.class); + entity.index = index; + return ok(views.html.details.render(entity)); + } + + private String getAuthorityResource(String id) { GetResponse response = index.client().prepareGet(config("index.name"), config("index.type"), id).get(); response().setHeader("Access-Control-Allow-Origin", "*"); if (!response.isExists()) { Logger.warn("{} does not exists in index, falling back to live version from GND", id); - return gnd(id); + return null; } - String jsonLd = response.getSourceAsString(); - return ok(prettyJsonString(Json.parse(jsonLd))).as(config("index.content")); + return response.getSourceAsString(); } public Result context() { @@ -188,4 +201,5 @@ private static String prettyJsonString(JsonNode jsonNode) { return null; } } + } diff --git a/app/models/AuthorityResource.java b/app/models/AuthorityResource.java new file mode 100644 index 0000000..48a8422 --- /dev/null +++ b/app/models/AuthorityResource.java @@ -0,0 +1,211 @@ +package models; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.tuple.Pair; +import org.elasticsearch.action.get.GetResponse; + +import controllers.HomeController; +import modules.IndexComponent; +import play.Logger; + +public class AuthorityResource { + + private final static String DNB_PREFIX = "http://d-nb.info/gnd/"; + + public enum Values { + JOINED, MULTI_LINE + } + + public IndexComponent index; + + private String id; + private List type; + + public Map definition; + public Map biographicalOrHistoricalInformation; + public List> hasGeometry; + public List gndIdentifier; + public List preferredName; + public List variantName; + public List sameAs; + public List geographicAreaCode; + public List relatedTerm; + public List relatedPerson; + public List relatedWork; + public List broaderTermInstantial; + public List broaderTermGeneral; + public List broaderTermPartitive; + public List dateOfConferenceOrEvent; + public List placeOfConferenceOrEvent; + public List spatialAreaOfActivity; + public List dateOfEstablishment; + public List placeOfBusiness; + public List wikipedia; + public List homepage; + public List topic; + public List gender; + public List professionOrOccupation; + public List precedingPlaceOrGeographicName; + public List succeedingPlaceOrGeographicName; + public List dateOfTermination; + public List academicDegree; + public List acquaintanceshipOrFriendship; + public List familialRelationship; + public List placeOfActivity; + public List dateOfBirth; + public List placeOfBirth; + public List placeOfDeath; + public List dateOfDeath; + public List professionalRelationship; + public List hierarchicalSuperiorOfTheCorporateBody; + public List firstAuthor; + public List publication; + public List dateOfProduction; + public List mediumOfPerformance; + public List firstComposer; + public List dateOfPublication; + + public String getId() { + return id.substring(DNB_PREFIX.length()); + } + + public void setId(String id) { + this.id = id; + } + + public List getType() { + return type.stream().filter(t -> !t.equals("AuthorityResource")).collect(Collectors.toList()); + } + + public void setType(List type) { + this.type = type; + } + + @Override + public String toString() { + return "AuthorityResource [id=" + id + "]"; + } + + public String title() { + return preferredName.get(0); + } + + public String subtitle() { + return getType().stream().collect(Collectors.joining("; ")); + } + + public List> generalFields() { + List> fields = new ArrayList<>(); + add("Bevorzugter Name", preferredName, Values.JOINED, fields); + add("Varianter Name", variantName, Values.JOINED, fields); + add("Entitätstyp", getType(), Values.JOINED, fields); + add("GND-ID", gndIdentifier, Values.JOINED, fields); + add("Ländercode", geographicAreaCode, Values.MULTI_LINE, fields); + add("Siehe auch", sameAs != null + ? sameAs.stream().filter(v -> !v.startsWith(DNB_PREFIX)).collect(Collectors.toList()) : sameAs, + Values.MULTI_LINE, fields); + return fields; + } + + public List> specialFields() { + List> fields = new ArrayList<>(); + @SuppressWarnings("unchecked") + Map location = hasGeometry != null + ? ((List>) hasGeometry.get(0).get("asWKT")).get(0) : null; + add("Definition", definition, Values.JOINED, fields); + add("Oberbegriff partitiv", broaderTermPartitive, Values.MULTI_LINE, fields); + add("Oberbegriff instantiell", broaderTermInstantial, Values.MULTI_LINE, fields); + add("Oberbegriff allgemein", broaderTermGeneral, Values.MULTI_LINE, fields); + add("Verwandter Begriff", relatedTerm, Values.MULTI_LINE, fields); + add("Veranstalungsdaten", dateOfConferenceOrEvent, Values.MULTI_LINE, fields); + add("Veranstaltungsort", placeOfConferenceOrEvent, Values.MULTI_LINE, fields); + add("Geographischer Wirkungsbereich", spatialAreaOfActivity, Values.MULTI_LINE, fields); + add("In Beziehung stehende Person", relatedPerson, Values.MULTI_LINE, fields); + add("Gründungsdatum", dateOfEstablishment, Values.JOINED, fields); + add("Sitz", placeOfBusiness, Values.MULTI_LINE, fields); + add("Wikipedia", wikipedia, Values.JOINED, fields); + add("Thema", topic, Values.JOINED, fields); + add("Homepage", homepage, Values.JOINED, fields); + add("Biografische oder historische Angaben", biographicalOrHistoricalInformation, Values.JOINED, fields); + add("Geschlecht", gender, Values.MULTI_LINE, fields); + add("Beruf oder Beschäftigung", professionOrOccupation, Values.MULTI_LINE, fields); + add("Vorheriges Geografikum", precedingPlaceOrGeographicName, Values.MULTI_LINE, fields); + add("Nachfolgendes Geografikum", succeedingPlaceOrGeographicName, Values.MULTI_LINE, fields); + add("Auflösungsdatum", dateOfTermination, Values.JOINED, fields); + add("Akademischer Grad", academicDegree, Values.JOINED, fields); + add("Bekannt mit", acquaintanceshipOrFriendship, Values.MULTI_LINE, fields); + add("Beziehung, Bekanntschaft, Freundschaft", familialRelationship, Values.MULTI_LINE, fields); + add("Wirkungsort", placeOfActivity, Values.MULTI_LINE, fields); + add("Geburtsdatum", dateOfBirth, Values.JOINED, fields); + add("Geburtsort", placeOfBirth, Values.JOINED, fields); + add("Sterbedatum", dateOfDeath, Values.JOINED, fields); + add("Sterbeort", placeOfDeath, Values.JOINED, fields); + add("In Beziehung stehendes Werk", relatedWork, Values.MULTI_LINE, fields); + add("Beruflich Beziehung", professionalRelationship, Values.MULTI_LINE, fields); + add("Erste Verfasserschaft", firstAuthor, Values.MULTI_LINE, fields); + add("Administrative Überordnung der Körperschaft", hierarchicalSuperiorOfTheCorporateBody, Values.MULTI_LINE, + fields); + add("Titelangabe", publication, Values.MULTI_LINE, fields); + add("Erstellungszeit", dateOfProduction, Values.MULTI_LINE, fields); + add("Besetzung im Musikbereich", mediumOfPerformance, Values.MULTI_LINE, fields); + add("Erster Komponist", firstComposer, Values.MULTI_LINE, fields); + add("Erscheinungszeit", dateOfPublication, Values.MULTI_LINE, fields); + add("Ort", location, Values.MULTI_LINE, fields); + return fields; + } + + private void add(String label, Map map, Values joined, List> fields) { + add(label, map != null ? map.values().stream().map(Object::toString).collect(Collectors.toList()) : null, + joined, fields); + } + + private void add(String label, List list, Values values, List> result) { + if (list != null && list.size() > 0) { + switch (values) { + case JOINED: { + String value = list.stream().map(v -> process(v)).collect(Collectors.joining("; ")); + result.add(Pair.of(label, value)); + break; + } + case MULTI_LINE: { + result.add(Pair.of(label, process(list.get(0)))); + list.subList(1, list.size()).forEach(e -> { + result.add(Pair.of("", process(e))); + }); + break; + } + } + } + } + + private String process(String string) { + String label = string; + String link = string; + if (string.startsWith(DNB_PREFIX)) { + label = labelFor(string.substring(DNB_PREFIX.length())); + link = string.replace(DNB_PREFIX, "/authorities/") + ".html"; + if (label == null) { + label = string; + link = string; + } + } + return string.startsWith("http") ? String.format("%s", link, label) : link; + } + + public String labelFor(String id) { + GetResponse response = index.client() + .prepareGet(HomeController.config("index.name"), HomeController.config("index.type"), id).get(); + if (!response.isExists()) { + Logger.warn("{} does not exists in index", id); + return null; + } + @SuppressWarnings("unchecked") + List preferredName = (List) response.getSourceAsMap().get("preferredName"); + return preferredName.get(0).toString(); + } + +} diff --git a/app/views/api.scala.html b/app/views/api.scala.html index 06e4192..5c74ba8 100644 --- a/app/views/api.scala.html +++ b/app/views/api.scala.html @@ -12,7 +12,7 @@

Search: @Html(controllers.routes.HomeController.search("text").toStrin } -

GET by ID: @Html(controllers.routes.HomeController.authority("<id>").toString)

+

GET by ID: @Html(controllers.routes.HomeController.authorityJson("<id>").toString)

@for((key,value) <- getSamples) { diff --git a/app/views/details.scala.html b/app/views/details.scala.html new file mode 100644 index 0000000..3cebf40 --- /dev/null +++ b/app/views/details.scala.html @@ -0,0 +1,29 @@ +@* Copyright 2017 Fabian Steeg, hbz. Licensed under the GPLv2 *@ + +@(resource: models.AuthorityResource) + +@import play.api.libs.json._ + +@main("", "lobid-resources - Details") { + +
+
+ + @for(f <- resource.generalFields) { + + } +
@f.getLeft@Html(f.getRight)
+
+ @if(resource.specialFields.size>0){ +
+ + @for(f <- resource.specialFields) { + + } +
@f.getLeft@Html(f.getRight)
+
+ } +
+} diff --git a/app/views/search.scala.html b/app/views/search.scala.html index f2f3a0b..57e104a 100644 --- a/app/views/search.scala.html +++ b/app/views/search.scala.html @@ -45,7 +45,7 @@ @result_short(id:String, doc: play.api.libs.json.JsValue, i: Int = -1) = { - @((doc \ "preferredName").asOpt[Seq[String]].getOrElse(Seq()).head) + @((doc \ "preferredName").asOpt[Seq[String]].getOrElse(Seq()).head) @((doc \ "variantName").asOpt[Seq[String]].getOrElse(Seq()).take(3).mkString("; ")) @((doc \ "gndIdentifier").asOpt[Seq[String]].getOrElse(Seq()).take(1)) diff --git a/conf/application.conf b/conf/application.conf index 1d09295..20478ce 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -78,4 +78,5 @@ icons { PlaceOrGeographicName : "fa fa-globe" Person : "fa fa-user" Family : "fa fa-users" + Gods : "glyphicon glyphicon-flash" } diff --git a/conf/routes b/conf/routes index 801dc4c..470872e 100644 --- a/conf/routes +++ b/conf/routes @@ -9,7 +9,9 @@ GET /authorities/api controllers.HomeController.api GET /authorities/context.jsonld controllers.HomeController.context() -GET /authorities/:id.json controllers.HomeController.authority(id) +GET /authorities/:id.json controllers.HomeController.authorityJson(id) + +GET /authorities/:id.html controllers.HomeController.authorityHtml(id) GET /authorities/search controllers.HomeController.search(q ?= "*", from: Int ?= 0, size: Int ?= 10, format ?= "html") diff --git a/public/images/json-ld.png b/public/images/json-ld.png new file mode 100644 index 0000000000000000000000000000000000000000..41d8549b2ddd316d675f3b618f038108ffc71819 GIT binary patch literal 2591 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 B@6iAN literal 0 HcmV?d00001 diff --git a/test/controllers/HomeControllerTest.java b/test/controllers/HomeControllerTest.java index 1ac7dd4..84d241c 100644 --- a/test/controllers/HomeControllerTest.java +++ b/test/controllers/HomeControllerTest.java @@ -26,11 +26,11 @@ public class HomeControllerTest extends WithApplication { public static Collection data() { return Arrays.asList(new Object[][] { // { routes.HomeController.index().toString(), Status.OK }, // - { routes.HomeController.authority("2-4").toString(), Status.OK }, // - { routes.HomeController.authority("1077774206").toString(), Status.OK }, // - { routes.HomeController.authority("1072719991").toString(), Status.OK }, // + { routes.HomeController.authorityJson("2-4").toString(), Status.OK }, // + { routes.HomeController.authorityJson("1077774206").toString(), Status.OK }, // + { routes.HomeController.authorityJson("1072719991").toString(), Status.OK }, // { routes.HomeController.search("*", 0, 10, "json").toString(), Status.OK }, - { routes.HomeController.authority("---").toString(), Status.NOT_FOUND } }); + { routes.HomeController.authorityJson("---").toString(), Status.NOT_FOUND } }); } public HomeControllerTest(String route, int status) { diff --git a/test/models/ModelTest.java b/test/models/ModelTest.java new file mode 100644 index 0000000..8dd759d --- /dev/null +++ b/test/models/ModelTest.java @@ -0,0 +1,32 @@ +package models; + +import java.io.FileNotFoundException; +import java.io.FileReader; + +import org.junit.Assert; +import org.junit.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; + +import apps.Convert; +import play.libs.Json; + +public class ModelTest { + + @Test + public void testModelCreation() throws FileNotFoundException { + String jsonLd = jsonLdFor("4074335-4"); + JsonNode json = Json.parse(jsonLd); + AuthorityResource res = Json.fromJson(json, AuthorityResource.class); + Assert.assertNotNull(res.getId()); + Assert.assertNotNull(res.getType()); + } + + private String jsonLdFor(String id) throws FileNotFoundException { + Model sourceModel = ModelFactory.createDefaultModel(); + sourceModel.read(new FileReader("test/ttl/" + id + ".ttl"), null, "TTL"); + return Convert.toJsonLd(id, sourceModel, true); + } +}