diff --git a/app/controllers/HomeController.java b/app/controllers/HomeController.java index b76cb4f..ec3276a 100644 --- a/app/controllers/HomeController.java +++ b/app/controllers/HomeController.java @@ -13,7 +13,13 @@ import java.nio.file.Paths; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; +import java.time.LocalDate; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoUnit; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -37,6 +43,7 @@ import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.QueryStringQueryBuilder; import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; @@ -101,8 +108,18 @@ public HomeController(WSClient httpClient) { @Inject IndexComponent index; + private Map> anniversaries = new HashMap<>(); + public static final Config CONFIG = ConfigFactory.load(); + private static final List ROUND_BIRTH = Arrays.asList(50, 70, 75, 100, 125, 150, 200, + 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000, 1100, + 1200, 1250, 1300, 1400, 1500, 1600, 1700, 1750, 1800, 1900, 2000); + + private static final List ROUND_DEATH = Arrays.asList(10, 25, 50, 75, 100, 125, 150, + 200, 250, 300, 350, 400, 450, 500, 600, 700, 750, 800, 900, 950, 1000, 1100, 1200, 1250, + 1300, 1400, 1500, 1600, 1700, 1750, 1800, 1900, 2000, 2100, 2200); + public static String config(String id) { return CONFIG.getString(id); } @@ -125,19 +142,133 @@ public Result index() { String queryString = String.format("depiction:* AND NOT gndIdentifier:(%s)", CONFIG.getStringList("dontShowOnMainPage").stream() .collect(Collectors.joining(" OR "))); - QueryStringQueryBuilder query = index.queryStringQuery(queryString); - FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(query, - ScoreFunctionBuilders.randomFunction(System.currentTimeMillis())); - SearchRequestBuilder requestBuilder = index.client().prepareSearch(config("index.prod.name")) - .setQuery(functionScoreQuery).setFrom(0).setSize(1); - SearchHits hits = requestBuilder.execute().actionGet().getHits(); + QueryBuilder query = randomScoreQuery(index.queryStringQuery(queryString)); + SearchHits hits = prepareSearch().setQuery(query).setFrom(0).setSize(1).execute() + .actionGet().getHits(); AuthorityResource entity = null; if (hits.getTotalHits() > 0) { SearchHit hit = hits.getAt(0); entity = entityWithImage(hit.getSourceAsString()); } JsonNode dataset = Json.parse(readFile(config("dataset.file"))); - return ok(views.html.index.render(entity, dataset, allHits())); + return ok(views.html.index.render(entity, dataset, allAnniversaries(), allHits())); + } + + private List allAnniversaries() { + // starting with any anniversaries on this day, fill up + // (to 5 overall) with random anniversaries from this month, + // retaining order (today first) and avoiding duplicates: + int fullSize = 5; + List anniversaries = anniversariesFor(thisDay()); + if (anniversaries.size() < fullSize) { + List thisMonth = anniversariesFor(thisMonth()); + for (int i = 0; anniversaries.size() < fullSize && i < thisMonth.size(); i++) { + if (!anniversaries.contains(thisMonth.get(i))) { + anniversaries.add(thisMonth.get(i)); + } + } + } + return anniversaries; + } + + private List anniversariesFor(String datePattern) { + // don't query on each reload... + List result = anniversaries.computeIfAbsent(datePattern, list -> { + QueryBuilder query = randomScoreQuery(anniversaryQuery(datePattern)); + SearchHits hits = prepareSearch().setQuery(query).setFrom(0).setSize((int) allHits()) + .execute().actionGet().getHits(); + return Arrays.asList(hits.getHits()).stream().filter(hit -> hasRound(datePattern, hit)) + .map(hit -> hitToAnniversary(datePattern, hit)).collect(Collectors.toList()); + }); + // ...but provide new selection/subset + Collections.shuffle(result); + return result.stream().limit(5).collect(Collectors.toList()); + + } + + private boolean hasRound(String datePattern, SearchHit hit) { + JsonNode json = Json.parse(hit.getSourceAsString()); + JsonNode birthNode = json.get("dateOfBirth"); + JsonNode deathNode = json.get("dateOfDeath"); + return isRound(birthNode, ROUND_BIRTH, datePattern) + || isRound(deathNode, ROUND_DEATH, datePattern); + } + + private boolean isRound(JsonNode node, List round, String datePattern) { + return node != null && is(datePattern, node.get(0).asText(), round); + } + + private FunctionScoreQueryBuilder randomScoreQuery(QueryBuilder query) { + return QueryBuilders.functionScoreQuery(query, + ScoreFunctionBuilders.randomFunction(System.currentTimeMillis())); + } + + private String hitToAnniversary(String datePattern, SearchHit hit) { + JsonNode fullJson = Json.parse("[" + hit.getSourceAsString() + "]"); + JsonNode suggestion = Json + .parse(toSuggestions(fullJson, + "preferredName,dateOfBirth-dateOfDeath,professionOrOccupation")) + .elements().next(); + JsonNode json = Json.toJson(ImmutableMap.of( // + "label", suggestion.get("label").asText(), // + "url", suggestion.get("id").asText().replace(AuthorityResource.GND_PREFIX, "/"), // + "details", details(datePattern, fullJson))); + return Json + .stringify(json); + } + + private String details(String datePattern, JsonNode json) { + String details = ""; + JsonNode dateOfBirth = json.findValue("dateOfBirth"); + String date; + if (dateOfBirth != null + && is(datePattern, date = dateOfBirth.get(0).asText(), ROUND_BIRTH)) { + details = String.format("%s. Geburtstag, geb. am %s", // + yearsSince(date), AuthorityResource.germanDate(date)); + } else if (is(datePattern, date = json.findValue("dateOfDeath").get(0).asText(), ROUND_DEATH)) { + details = String.format("%s. Todestag, gest. am %s", // + yearsSince(date), AuthorityResource.germanDate(date)); + } + return details; + } + + private boolean is(String datePattern, String date, List round) { + return date.contains(datePattern.replace("*", "")) + && round.contains((int) yearsSince(date)); + } + + private SearchRequestBuilder prepareSearch() { + return index.client().prepareSearch(config("index.prod.name")); + } + + private QueryStringQueryBuilder anniversaryQuery(String today) { + String queryString = String.format( + "(dateOfBirth:(%s) OR dateOfDeath:(%s)) AND NOT gndIdentifier:(%s) AND _exists_:dateOfDeath", + today, today, + CONFIG.getStringList("dontShowOnMainPage").stream() + .collect(Collectors.joining(" OR "))); + QueryStringQueryBuilder query = index.queryStringQuery(queryString); + return query; + } + + private long yearsSince(String date) { + try { + int year = Integer.parseInt(AuthorityResource.cleanDate(date).substring(0, 4)); + return ChronoUnit.YEARS.between( // + LocalDate.of(year, 1, 1), LocalDate.of(ZonedDateTime.now().getYear(), 1, 1)); + } catch (DateTimeParseException e) { + e.printStackTrace(); + return -1; + } + } + + public static String thisDay() { + return ZonedDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE).replaceAll("\\d{4}", + "*"); // e.g return "*-01-09"; + } + + private static String thisMonth() { + return thisDay().replaceAll("\\d{2}$", "*"); // e.g. return "*-01-*"; } /** @@ -408,7 +539,7 @@ private Result jsonLines(String q, String filter, SearchResponse response) { query = query.filter(index.queryStringQuery(filter)); } TimeValue keepAlive = new TimeValue(60000); - SearchRequestBuilder scrollRequest = index.client().prepareSearch(config("index.prod.name")) + SearchRequestBuilder scrollRequest = prepareSearch() .addSort(FieldSortBuilder.DOC_FIELD_NAME, SortOrder.ASC).setScroll(keepAlive).setQuery(query) .setSize(100 /* hits per shard for each scroll */); Logger.debug("Scrolling with query: q={}, request={}", q, scrollRequest); diff --git a/app/models/AuthorityResource.java b/app/models/AuthorityResource.java index a33a0e9..e589a84 100644 --- a/app/models/AuthorityResource.java +++ b/app/models/AuthorityResource.java @@ -4,6 +4,7 @@ import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -491,9 +492,20 @@ private String process(String field, String value, String label, int i, int size return withDefaultHidden(field, size, i, result); } - private String germanDate(String value) { - return LocalDate.from(DateTimeFormatter.ISO_LOCAL_DATE.parse(value)) - .format(DateTimeFormatter.ofPattern("dd.MM.uuuu", Locale.GERMAN)); + public static String germanDate(String value) { + try { + return LocalDate + .from(DateTimeFormatter.ISO_LOCAL_DATE.parse(cleanDate(value))) + .format(DateTimeFormatter.ofPattern("dd.MM.uuuu", Locale.GERMAN)); + } catch (DateTimeParseException e) { + e.printStackTrace(); + return value; + } + + } + + public static String cleanDate(String value) { + return value.replaceAll(".*(\\d{4}-\\d{2}-\\d{2}).*", "$1"); } private String withDefaultHidden(String field, int size, int i, String result) { diff --git a/app/views/index.scala.html b/app/views/index.scala.html index 4a3098c..5927680 100644 --- a/app/views/index.scala.html +++ b/app/views/index.scala.html @@ -1,9 +1,12 @@ @* Copyright 2015-2018 Fabian Steeg, hbz. Licensed under the EPL 2.0 *@ -@(entity: AuthorityResource, dataset: com.fasterxml.jackson.databind.JsonNode, allHits: Long) +@(entity: AuthorityResource, dataset: com.fasterxml.jackson.databind.JsonNode, anniversaries: List[String], allHits: Long) @import helper._ @import controllers.HomeController.formatCount +@import controllers.HomeController.thisDay +@import play.api.libs.json._ +@import controllers.HomeController.CONFIG @main("", "RPPD", allHits) {
-
-

- @Html(dataset.get("description").get("de").asText()) -

-
- @if(entity!=null){
-
- Darstellung von @entity.preferredName -
@entity.preferredName
@if(entity.imageAttribution!=null){(@Html(entity.imageAttribution))}
-
-
} +
+

Jubiläen

+ @for(resource <- anniversaries) { +
+ + @for(json <- Json.parse(resource).asOpt[JsValue]; + label <- (json \ "label").asOpt[String]; + details <- (json \ "details").asOpt[String]; + url = (json \ "url").asOpt[String]) { + @label +

@details

+ } +
+
+ } +

Aktuelle Jahrestage

+
+
+ @Html(dataset.get("description").get("de").asText()) +
+ @if(entity!=null){
+
+ Darstellung von @entity.preferredName +
@entity.preferredName
@if(entity.imageAttribution!=null){(@Html(entity.imageAttribution))}
+
+
}