diff --git a/src/main/java/chatbot/Application.java b/src/main/java/chatbot/Application.java index 9b80496..519781d 100644 --- a/src/main/java/chatbot/Application.java +++ b/src/main/java/chatbot/Application.java @@ -46,7 +46,8 @@ public class Application { private static final Logger logger = LoggerFactory.getLogger(Application.class); @Bean - public MessengerSendClient initializeFBMessengerSendClient(@Value("${chatbot.fb.pageAccessToken}") String pageAccessToken) { + public MessengerSendClient initializeFBMessengerSendClient( + @Value("${chatbot.fb.pageAccessToken}") String pageAccessToken) { logger.info("Initializing MessengerSendClient - pageAccessToken: {}", pageAccessToken); return MessengerPlatform.newSendClientBuilder(pageAccessToken).build(); } @@ -55,10 +56,14 @@ public MessengerSendClient initializeFBMessengerSendClient(@Value("${chatbot.fb. static class AssetsConfiguration extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { - registry.addResourceHandler("/assets/**").addResourceLocations("file:node_modules/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)); - registry.addResourceHandler("/js/**").addResourceLocations("file:src/main/app/js/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)); - registry.addResourceHandler("/css/**").addResourceLocations("file:src/main/app/css/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)); - registry.addResourceHandler("/images/**").addResourceLocations("file:src/main/resources/static/images/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)); + registry.addResourceHandler("/assets/**").addResourceLocations("file:node_modules/") + .setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)); + registry.addResourceHandler("/js/**").addResourceLocations("file:src/main/app/js/") + .setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)); + registry.addResourceHandler("/css/**").addResourceLocations("file:src/main/app/css/") + .setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)); + registry.addResourceHandler("/images/**").addResourceLocations("file:src/main/resources/static/images/") + .setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)); super.addResourceHandlers(registry); } } @@ -74,7 +79,9 @@ protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() - .antMatchers("/", "/assets/**/*", "/js/*", "/images/**/*", "/feedback", "/webhook", "/fbwebhook", "/slackwebhook", "/embed").permitAll() + .antMatchers("/", "/assets/**/*", "/js/*", "/images/**/*", "/feedback", "/webhook", "/fbwebhook", + "/slackwebhook", "/embed") + .permitAll() .anyRequest().authenticated() .and() .formLogin() @@ -88,7 +95,9 @@ protected void configure(HttpSecurity http) throws Exception { } @Autowired - protected void configureGlobal(AuthenticationManagerBuilder auth, @Value("${admin.username}") String username, @Value("${admin.password}") String password, @Value("${chatbot.baseUrl}") String baseUrl) throws Exception { + protected void configureGlobal(AuthenticationManagerBuilder auth, @Value("${admin.username}") String username, + @Value("${admin.password}") String password, @Value("${chatbot.baseUrl}") String baseUrl) + throws Exception { this.baseUrl = baseUrl; auth .inMemoryAuthentication() @@ -108,21 +117,18 @@ public static class Helper { @Autowired public Helper(final CloudantClient cloudantClient, - WolframRepository wolframRepository, - @Value("${cloudant.chatDB}") String chatDBName, - @Value("${cloudant.feedbackDB}") String feedbackDBName, - @Value("${cloudant.explorerDB}") String explorerDBName, - @Value("${tmdb.apiKey}") String tmdbApiKey) { + WolframRepository wolframRepository, + @Value("${cloudant.chatDB}") String chatDBName, + @Value("${cloudant.feedbackDB}") String feedbackDBName, + @Value("${cloudant.explorerDB}") String explorerDBName, + @Value("${tmdb.apiKey}") String tmdbApiKey) { try { chatDB = cloudantClient.database(chatDBName, true); feedbackDB = cloudantClient.database(feedbackDBName, true); explorerDB = cloudantClient.database(explorerDBName, true); - } - catch(Exception e) { - logger.info("ERROR HERE"); - e.printStackTrace(); - } - finally { + } catch (Exception e) { + logger.error("CouchDB / Cloudant is unavailable. Explorer properties will not be loaded.", e); + } finally { this.tmdbApiKey = tmdbApiKey; this.wolframRepository = wolframRepository; @@ -130,11 +136,14 @@ public Helper(final CloudantClient cloudantClient, eliza = new ElizaMain(); eliza.readScript(true, "src/main/resources/eliza/script"); - sparql = new SPARQL(explorerDB); + sparql = (explorerDB != null) + ? new SPARQL(explorerDB) + : SPARQL.disabled(); + languageTool = new JLanguageTool(new AmericanEnglish()); for (Rule rule : languageTool.getAllActiveRules()) { if (rule instanceof SpellingCheckRule) { - List wordsToIgnore = Arrays.asList(new String[] {"nlp", "merkel"}); + List wordsToIgnore = Arrays.asList(new String[] { "nlp", "merkel" }); ((SpellingCheckRule) rule).addIgnoreTokens(wordsToIgnore); } } @@ -169,6 +178,10 @@ public Database getExplorerDB() { return explorerDB; } + public boolean isExplorerDBAvailable() { + return explorerDB != null; + } + public SPARQL getSparql() { return sparql; } diff --git a/src/main/java/chatbot/lib/api/SPARQL.java b/src/main/java/chatbot/lib/api/SPARQL.java index 64cbf43..db4e837 100644 --- a/src/main/java/chatbot/lib/api/SPARQL.java +++ b/src/main/java/chatbot/lib/api/SPARQL.java @@ -29,39 +29,75 @@ public class SPARQL { private static final String ENDPOINT = "https://dbpedia.org/sparql"; private static final String PREFIXES = new String( "PREFIX foaf: \n" + - "PREFIX rdf: \n"+ - "PREFIX rdfs: \n" + - "PREFIX dbo: \n" + - "PREFIX dbp: \n" + - "PREFIX dbr: \n" + - "PREFIX dct: \n" - ); + "PREFIX rdf: \n" + + "PREFIX rdfs: \n" + + "PREFIX dbo: \n" + + "PREFIX dbp: \n" + + "PREFIX dbr: \n" + + "PREFIX dct: \n"); Database explorerDB; + public SPARQL(Database explorerDB) { this.explorerDB = explorerDB; } + /** + * Safe disabled SPARQL instance used when CouchDB is unavailable. + * Prevents NullPointerExceptions across the application. + */ + public static SPARQL disabled() { + return new SPARQL(null) { + + @Override + public ResponseData getEntityInformation(String uri) { + ResponseData response = new ResponseData(); + response.setTitle("Explorer database unavailable"); + response.setText( + "The DBpedia explorer database is currently unavailable. " + + "Please try again later."); + return response; + } + + @Override + public String getLabel(String uri) { + return "Explorer database unavailable"; + } + + @Override + public int isDisambiguationPage(String uri) { + return 0; + } + + @Override + public ArrayList getEntitiesByURIs(String uris) { + // CouchDB down → return safe empty result + return new ArrayList<>(); + } + }; + } + private static final String ENTITY_SELECT_PARAMETERS = " ?label ?abstract ?primaryTopic ?thumbnail ?description "; public String buildQuery(String query) { return PREFIXES + query; } - // Remove the pronounciation information that appears at the beginning of the article enclosed by () - // Additionally can be checked to contain unnecessary characters instead of blindly stripping based on brackets + // Remove the pronounciation information that appears at the beginning of the + // article enclosed by () + // Additionally can be checked to contain unnecessary characters instead of + // blindly stripping based on brackets private String stripWikiepdiaContent(String text) { int indexStart = text.indexOf("("), indexEnd; - if(indexStart > 0) { + if (indexStart > 0) { indexEnd = text.indexOf(")", indexStart) + 2; - if(indexEnd != -1) { + if (indexEnd != -1) { return text.replace(text.substring(indexStart, indexEnd), ""); } - } - else if(indexStart == 0) { + } else if (indexStart == 0) { // When abstract starts with info on Disambiguation indexEnd = text.lastIndexOf("(disambiguation).)"); - if(indexEnd != -1) { + if (indexEnd != -1) { return text.replace(text.substring(indexStart, indexEnd + 18), ""); } } @@ -69,7 +105,7 @@ else if(indexStart == 0) { } private String processWikipediaAbstract(String abs) { - while(abs.indexOf("(") != -1) { + while (abs.indexOf("(") != -1) { abs = stripWikiepdiaContent(abs); } return abs; @@ -118,15 +154,15 @@ private List getRelevantProperties(String uri, List types, S .inclusiveEnd(true) .build().getResponse().getValues(); - - for(ExplorerProperties property : explorerProperties) { - // Check if the property matches one of the list of classes(types) found for the entity - if(types.contains(property.getClassName())) { + for (ExplorerProperties property : explorerProperties) { + // Check if the property matches one of the list of classes(types) found for the + // entity + if (types.contains(property.getClassName())) { propertyMap.put(Float.parseFloat(property.getScore()), property.getProperty()); } } - if(propertyMap.size() > 0) { + if (propertyMap.size() > 0) { int count = 0; Iterator iterator = propertyMap.descendingKeySet().iterator(); String property_uris = ""; @@ -135,30 +171,33 @@ private List getRelevantProperties(String uri, List types, S count++; } - String query = buildQuery("SELECT ?property_label (group_concat(distinct ?value;separator='__') as ?values) (group_concat(distinct ?value_label;separator='__') as ?value_labels) where {\n" + - "VALUES ?property {" + property_uris + "}\n" + - "<" + uri + "> ?property ?value . \n" + - "?property rdfs:label ?property_label . FILTER(lang(?property_label)='en'). \n" + - "OPTIONAL {?value rdfs:label ?value_label . FILTER(lang(?value_label) = 'en') }\n" + - "} GROUP BY ?property_label"); + String query = buildQuery( + "SELECT ?property_label (group_concat(distinct ?value;separator='__') as ?values) (group_concat(distinct ?value_label;separator='__') as ?value_labels) where {\n" + + + "VALUES ?property {" + property_uris + "}\n" + + "<" + uri + "> ?property ?value . \n" + + "?property rdfs:label ?property_label . FILTER(lang(?property_label)='en'). \n" + + "OPTIONAL {?value rdfs:label ?value_label . FILTER(lang(?value_label) = 'en') }\n" + + "} GROUP BY ?property_label"); QueryExecution queryExecution = executeQuery(query); try { Iterator results = queryExecution.execSelect(); - while(results.hasNext()) { + while (results.hasNext()) { QuerySolution result = results.next(); ResponseData.Field field = new ResponseData.Field(); field.setName(Utility.capitalizeAll(result.get("property_label").asLiteral().getString())); - // If Value Label String is empty then we use Value String instead which means the value is a literal. So we are only taking the first element before space - if(result.get("value_labels").asLiteral().getString().equals("")) { - field.setValue(Utility.capitalizeAll(result.get("values").asLiteral().getString().split("__")[0])); - } - else { + // If Value Label String is empty then we use Value String instead which means + // the value is a literal. So we are only taking the first element before space + if (result.get("value_labels").asLiteral().getString().equals("")) { + field.setValue( + Utility.capitalizeAll(result.get("values").asLiteral().getString().split("__")[0])); + } else { LinkedHashMap map = new LinkedHashMap<>(); String[] keyArray = result.get("values").asLiteral().getString().split("__"); String[] valueArray = result.get("value_labels").asLiteral().getString().split("__"); - for(int index = 0; index < keyArray.length; index++) { + for (int index = 0; index < keyArray.length; index++) { map.put(Utility.convertDBpediaToWikipediaURL(keyArray[index]), valueArray[index]); } field.setValues(map); @@ -166,17 +205,14 @@ private List getRelevantProperties(String uri, List types, S fields.add(field); } return fields; - } - finally { + } finally { queryExecution.close(); return fields; } - } - else { + } else { return fields; } - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); } return fields; @@ -187,23 +223,24 @@ private ResponseData processEntityInformation(String uri, QuerySolution result) ResponseData responseData = new ResponseData(); String label = result.get("label").asLiteral().getString(), text = ""; responseData.setTitle(label); - responseData.addButton(new ResponseData.Button("View in Wikipedia", ResponseType.BUTTON_LINK, result.get("primaryTopic").toString())); + responseData.addButton(new ResponseData.Button("View in Wikipedia", ResponseType.BUTTON_LINK, + result.get("primaryTopic").toString())); node = result.get("thumbnail"); - if(node != null) { + if (node != null) { responseData.setImage(node.toString()); } -// String summary = new GenesisService().getSummary(uri); -// if(summary == null || summary.isEmpty()) { -// node = result.get(VAR_ABSTRACT); -// if(node != null) { -// summary = processWikipediaAbstract(node.asLiteral().getString()); -// } -// } + // String summary = new GenesisService().getSummary(uri); + // if(summary == null || summary.isEmpty()) { + // node = result.get(VAR_ABSTRACT); + // if(node != null) { + // summary = processWikipediaAbstract(node.asLiteral().getString()); + // } + // } node = result.get("description"); - if(node != null) { + if (node != null) { text += node.asLiteral().getString() + "\n\n"; } @@ -218,35 +255,39 @@ private ResponseData processEntityInformation(String uri, QuerySolution result) } String query = buildQuery( - "SELECT (GROUP_CONCAT(distinct ?type;separator=' ') as ?types) (GROUP_CONCAT(distinct ?property;separator=' ') as ?properties) WHERE {\n" + - "<" + uri + "> rdf:type ?type . FILTER(STRSTARTS(STR(?type), 'http://dbpedia.org/ontology')) . \n" + - "<" + uri + "> ?property ?value . FILTER(STRSTARTS(STR(?property), 'http://dbpedia.org/ontology')) . \n" + - "}"); + "SELECT (GROUP_CONCAT(distinct ?type;separator=' ') as ?types) (GROUP_CONCAT(distinct ?property;separator=' ') as ?properties) WHERE {\n" + + + "<" + uri + + "> rdf:type ?type . FILTER(STRSTARTS(STR(?type), 'http://dbpedia.org/ontology')) . \n" + + "<" + uri + + "> ?property ?value . FILTER(STRSTARTS(STR(?property), 'http://dbpedia.org/ontology')) . \n" + + "}"); QueryExecution queryExecution = executeQuery(query); try { Iterator results = queryExecution.execSelect(); - while(results.hasNext()) { + while (results.hasNext()) { QuerySolution solution = results.next(); - if(solution.get("types") != null && solution.get("properties") != null) { + if (solution.get("types") != null && solution.get("properties") != null) { List types = Arrays.asList(solution.get("types").asLiteral().getString().split(" ")); String[] properties = solution.get("properties").asLiteral().getString().split(" "); responseData.setFields(getRelevantProperties(uri, types, properties)); } } - } - finally { + } finally { queryExecution.close(); } responseData.addButton(new ResponseData.Button("View in DBpedia", ResponseType.BUTTON_LINK, uri)); - responseData.addButton(new ResponseData.Button("Learn More", ResponseType.BUTTON_PARAM, TemplateType.LEARN_MORE + Utility.STRING_SEPARATOR + uri + Utility.STRING_SEPARATOR + label)); + responseData.addButton(new ResponseData.Button("Learn More", ResponseType.BUTTON_PARAM, + TemplateType.LEARN_MORE + Utility.STRING_SEPARATOR + uri + Utility.STRING_SEPARATOR + label)); return responseData; } private String getEntityWhereCondition(String uri) { // URI could either be the actual URI or a variable reference - // In case of actual URI it needs to be enclosed with which is not required for variable reference - if(!uri.startsWith("?")) { + // In case of actual URI it needs to be enclosed with which is not + // required for variable reference + if (!uri.startsWith("?")) { uri = "<" + uri + ">"; } return uri + " rdfs:label ?label .\n" + @@ -260,18 +301,17 @@ private String getEntityWhereCondition(String uri) { public ResponseData getEntityInformation(String uri) { String query = buildQuery("SELECT " + ENTITY_SELECT_PARAMETERS + " WHERE {" + getEntityWhereCondition(uri) + - "}"); + "}"); QueryExecution queryExecution = executeQuery(query); ResponseData responseData = null; try { Iterator results = queryExecution.execSelect(); - while(results.hasNext()) { + while (results.hasNext()) { QuerySolution result = results.next(); responseData = processEntityInformation(uri, result); } - } - finally { + } finally { queryExecution.close(); } return responseData; @@ -280,23 +320,23 @@ public ResponseData getEntityInformation(String uri) { /** * Returns 0 if it is not a disambiguation page. Returns count otherwise * We can only show 10 at a time so its limited by that + * * @return 0 or count */ public int isDisambiguationPage(String uri) { - String query = buildQuery("SELECT (count(*) as ?count) WHERE {" + - "<" + uri + "> ?o." + - "}"); + String query = buildQuery("SELECT (count(*) as ?count) WHERE {" + + "<" + uri + "> ?o." + + "}"); QueryExecution queryExecution = executeQuery(query); int count = 0; try { Iterator results = queryExecution.execSelect(); - while(results.hasNext()) { + while (results.hasNext()) { QuerySolution result = results.next(); count = result.get("count").asLiteral().getInt(); } - } - finally { + } finally { queryExecution.close(); } return count; @@ -310,13 +350,12 @@ private ArrayList getEntities(String query) { Iterator results = queryExecution.execSelect(); if (results != null) { responseDatas = new ArrayList<>(); - while(results.hasNext()) { + while (results.hasNext()) { QuerySolution result = results.next(); responseDatas.add(processEntityInformation(result.get("uri").toString(), result)); } } - } - finally { + } finally { queryExecution.close(); } return responseDatas; @@ -326,7 +365,7 @@ public ArrayList getDisambiguatedEntities(String uri, int offset, String query = buildQuery("SELECT ?uri " + ENTITY_SELECT_PARAMETERS + " WHERE {\n" + "<" + uri + "> ?uri .\n" + getEntityWhereCondition("?uri") + - "} ORDER BY ?uri OFFSET " + offset + " LIMIT " + limit); + "} ORDER BY ?uri OFFSET " + offset + " LIMIT " + limit); return getEntities(query); } @@ -349,8 +388,7 @@ public String getLabel(String uri) { try { Iterator results = queryExecution.execSelect(); label = results.next().get("label").asLiteral().getString(); - } - finally { + } finally { queryExecution.close(); } return label; @@ -360,19 +398,18 @@ public String getRDFTypes(String uri) { String types = null; String query = buildQuery("SELECT (group_concat(?type;separator=' ') as ?types) WHERE {" + "<" + uri + "> rdf:type ?type ." + - "}"); + "}"); QueryExecution queryExecution = executeQuery(query); try { Iterator results = queryExecution.execSelect(); if (results != null) { - while(results.hasNext()) { + while (results.hasNext()) { QuerySolution result = results.next(); types = result.get("types").toString(); } } - } - finally { + } finally { queryExecution.close(); } return types;