Skip to content

Commit

Permalink
Merge pull request #103 from Onlineberatung/OB-7091-mailservice-and-w…
Browse files Browse the repository at this point in the history
…eblate-integration

Ob 7091 mailservice and weblate integration
  • Loading branch information
tkuzynow authored Dec 7, 2023
2 parents 007dc06 + 927c141 commit a8084a0
Show file tree
Hide file tree
Showing 26 changed files with 520 additions and 52 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ VOLUME ["/tmp","/log"]
EXPOSE 8080
ARG JAR_FILE
COPY ./MailService.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
ENTRYPOINT ["java","-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
38 changes: 38 additions & 0 deletions ehcache.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<config
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xsi:schemaLocation="
http://www.ehcache.org/v3
http://www.ehcache.org/schema/ehcache-core-3.7.xsd">

<cache alias="translations">
<expiry>
<ttl unit="seconds">60</ttl>
</expiry>

<resources>
<offheap unit="MB">10</offheap>
</resources>
</cache>

<cache-template name="default">
<expiry>
<ttl unit="seconds">60</ttl>
</expiry>
<listeners>
<listener>
<class>de.caritas.cob.mailservice.api.cache.CacheEventLogger</class>
<event-firing-mode>ASYNCHRONOUS</event-firing-mode>
<event-ordering-mode>UNORDERED</event-ordering-mode>
<events-to-fire-on>CREATED</events-to-fire-on>
<events-to-fire-on>EXPIRED</events-to-fire-on>
<events-to-fire-on>EVICTED</events-to-fire-on>
</listener>
</listeners>
<resources>
<heap>1000</heap>
<offheap unit="MB">10</offheap>
<disk persistent="true" unit="MB">20</disk>
</resources>
</cache-template>
</config>
13 changes: 13 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package de.caritas.cob.mailservice.api;

import de.caritas.cob.mailservice.api.service.TranslationService;
import java.util.Locale;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.NoSuchMessageException;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
public class RestApiMessageSource implements MessageSource {


public final @NonNull TranslationService translationService;

@Override
public String getMessage(String code, Object[] args, String defaultMessage, Locale locale) {
log.info("getMessage called with code: {}, args: {}, defaultMessage: {}, locale: {}", code,
args, defaultMessage, locale);
return translationService.fetchTranslations(locale.getLanguage()).get(code);
}

@Override
public String getMessage(String code, Object[] args, Locale locale)
throws NoSuchMessageException {
return getMessage(code, args, null, locale);
}

@Override
public String getMessage(MessageSourceResolvable resolvable, Locale locale)
throws NoSuchMessageException {
if (resolvable == null) {
log.warn("getMessage called with null resolvable");
return null;
}
return getMessage(resolvable.getCodes()[0], null, locale);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package de.caritas.cob.mailservice.api.cache;

import lombok.extern.slf4j.Slf4j;
import org.ehcache.event.CacheEvent;
import org.ehcache.event.CacheEventListener;

@Slf4j
public class CacheEventLogger implements CacheEventListener<Object, Object> {

@Override
public void onEvent(CacheEvent<? extends Object, ? extends Object> cacheEvent) {
log.info(
"caching event: ", cacheEvent.getKey(), cacheEvent.getOldValue(), cacheEvent.getNewValue());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package de.caritas.cob.mailservice.api.controller;

import de.caritas.cob.mailservice.api.service.TranslationService;
import java.util.Map;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;


@Controller
@RequiredArgsConstructor
public class TranslationController {

private final @NonNull TranslationService translationService;

@GetMapping(value = "/translations")
public ResponseEntity<Map<String, String>> getTranslations() {
var result = translationService.fetchTranslations("de");
return new ResponseEntity<>(result, org.springframework.http.HttpStatus.OK);
}

@GetMapping(value = "/translations/evict")
@ResponseBody
public String evictTranslationCache() {
translationService.evictCache();
return "Cache evicted";
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package de.caritas.cob.mailservice.api.helper;

import de.caritas.cob.mailservice.api.model.LanguageCode;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import javax.annotation.PostConstruct;
Expand All @@ -21,9 +23,11 @@ void init() {
templateEngine = tempTemplateEngine;
}

public static Optional<String> getProcessedHtml(Map<String, Object> data, String templateName) {
public static Optional<String> getProcessedHtml(Map<String, Object> data, LanguageCode languageCode, String templateName) {

Context context = new Context();
Locale locale = Locale.forLanguageTag(languageCode.getValue());
context.setLocale(locale);

if (data != null) {
data.forEach(context::setVariable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ private void loadRequiredMailDataAndSendMail(MailDTO mail, TemplateDescription d

private void renderAndSend(MailDTO mail, TemplateDescription desc, Map<String, Object> data) {
var subject = templateService.getRenderedSubject(desc, data, mail.getLanguage());

try {
templateService
.render(desc, mail.getTemplate(), data, mail.getLanguage())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package de.caritas.cob.mailservice.api.service;

import static de.caritas.cob.mailservice.api.helper.ThymeleafHelper.getProcessedHtml;

import de.caritas.cob.mailservice.api.exception.TemplateServiceException;
import de.caritas.cob.mailservice.api.helper.ThymeleafHelper;
import de.caritas.cob.mailservice.api.mailtemplate.TemplateDescription;
import de.caritas.cob.mailservice.api.model.LanguageCode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.apache.commons.text.StringSubstitutor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
Expand All @@ -18,6 +21,7 @@
* Service for template processing
**/
@Service
@RequiredArgsConstructor
public class TemplateService {

@Value("${app.imprint.url}")
Expand All @@ -26,6 +30,9 @@ public class TemplateService {
@Value("${app.dataprivacy.url}")
private String dataPrivacyUrl;

@NonNull
private final TranslationService translationService;

/**
* Get the processed html template with replaced placeholders
*
Expand All @@ -41,6 +48,7 @@ public Optional<String> render(TemplateDescription desc, String name,
data.put("urlimpressum", imprintUrl);
data.put("urldatenschutz", dataPrivacyUrl);

data.putAll(getTranslationMapAndDefaultToGermanIfNotFound(language));
List<String> missingFieldList = getMissingTemplateFields(desc, data);

if (!CollectionUtils.isEmpty(missingFieldList)) {
Expand All @@ -51,7 +59,20 @@ public Optional<String> render(TemplateDescription desc, String name,

var templateFilename = desc.getTemplateFilenameOrFallback(language);

return ThymeleafHelper.getProcessedHtml(data, templateFilename);
return translationsArePresentAndNotEmpty(language) ? getProcessedHtml(data, language, templateFilename) :
getProcessedHtml(data, LanguageCode.DE, templateFilename);
}

private boolean translationsArePresentAndNotEmpty(LanguageCode language) {
var translations = translationService.tryFetchTranslations(
language.getValue());
return translations.isPresent() && !translations.get().isEmpty();
}

private Map<String, String> getTranslationMapAndDefaultToGermanIfNotFound(LanguageCode language) {
return translationService.tryFetchTranslations(language.getValue()).orElse(
translationService.tryFetchTranslations(LanguageCode.DE.getValue())
.orElse(new HashMap<>()));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package de.caritas.cob.mailservice.api.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.caritas.cob.mailservice.config.apiclient.TranlationMangementServiceApiClient;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;

@Service
@Slf4j
public class TranslationService {

@Value("${weblate.api.url}")
private String apiUrl;

@Value("${weblate.api.key}")
private String apiKey;

@Value("${weblate.project}")
private String project;

@Value("${weblate.component}")
private String component;

private final @NonNull TranlationMangementServiceApiClient tranlationMangementServiceApiClient;

public TranslationService(TranlationMangementServiceApiClient tranlationMangementServiceApiClient) {
this.tranlationMangementServiceApiClient = tranlationMangementServiceApiClient;
}

@Cacheable(value = "translations")
public Map<String, String> fetchTranslations(String languageCode) {
try {

return fetchTranslationAsMap(languageCode);
} catch (JsonProcessingException ex) {
throw new TranslationServiceException(String.format(
"Json file with translations could not be parsed, translation component name: %s",
component), ex);
}
}

@CacheEvict(value = "translations", allEntries = true)
public void evictCache() {
log.info("Evicting translations cache");
}

private Map<String, String> fetchTranslationAsMap(String languageCode) throws JsonProcessingException {
String translations = fetchTranslationsAsString(languageCode);
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(translations, Map.class);
}

public Optional<Map<String, String>> tryFetchTranslations(String languageCode) {

try {
var result = fetchTranslationAsMap(languageCode);
return Optional.of(result);
} catch (JsonProcessingException e) {
log.warn("Error while processing json file with translations. Returning empty translations", e);
return Optional.empty();
}

}

private String fetchTranslationsAsString(String languageCode) {
try {
return tranlationMangementServiceApiClient.tryFetchTranslationsFromTranslationManagementService(project, component,
languageCode);
} catch (HttpClientErrorException e) {
if (HttpStatus.NOT_FOUND.equals(e.getStatusCode())) {
log.warn("Translations for component {}, language {} not found in weblate, returning default translations", component,
languageCode);
return fetchDefaultTranslations(component, languageCode);
} else {
log.error("Error while fetching translations from translation management service", e);
throw e;
}
}
}



private String fetchDefaultTranslations(String translationComponentName, String languageCode) {
var inputStream = TranslationService.class.getResourceAsStream(
getTranslationFilename(translationComponentName + "." + languageCode));
if (inputStream == null) {
return "{}";
}
try {
final List<String> fileLines = IOUtils
.readLines(inputStream, StandardCharsets.UTF_8.displayName());
return String.join("", fileLines);
} catch (IOException ex) {
throw new IllegalStateException(String.format(
"Json file with translations could not be loaded, translation component name: %s",
translationComponentName), ex);
}
}

private String getTranslationFilename(String templateName) {
return "/i18n/" + templateName.toLowerCase() + ".json";
}

private class TranslationServiceException extends RuntimeException {

public TranslationServiceException(String format, JsonProcessingException ex) {
super(format, ex);
}
}
}
Loading

0 comments on commit a8084a0

Please sign in to comment.