diff --git a/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json new file mode 100644 index 0000000000..a405e36d23 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json @@ -0,0 +1,5 @@ +{ + "git": { + "description": "die git repo ist super " + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json new file mode 100644 index 0000000000..0412a162b3 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json @@ -0,0 +1,5 @@ +{ + "git": { + "description": "the git repo is great " + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json new file mode 100644 index 0000000000..49a3d97570 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json @@ -0,0 +1,5 @@ +{ + "hg": { + "description": "die hg repo ist super " + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json new file mode 100644 index 0000000000..28c628e81e --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json @@ -0,0 +1,5 @@ +{ + "hg": { + "description": "the hg repo is great " + } +} diff --git a/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json new file mode 100644 index 0000000000..1c25a5bcf5 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json @@ -0,0 +1,5 @@ +{ + "svn": { + "description": "die svn repo ist super " + } +} diff --git a/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json new file mode 100644 index 0000000000..cbfbaac63b --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json @@ -0,0 +1,5 @@ +{ + "svn": { + "description": "the svn repo is great " + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java b/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java index 6b4a29961d..6405c16ddc 100644 --- a/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java @@ -33,7 +33,7 @@ public class WebResourceServlet extends HttpServlet { * TODO remove old protocol servlets and hook. Move /hook/hg to api? */ @VisibleForTesting - static final String PATTERN = "/(?!api/|git/|hg/|svn/|hook/|repo/).*"; + static final String PATTERN = "/(?!api/|git/|hg/|svn/|hook/|repo/|locales/).*"; private static final Logger LOG = LoggerFactory.getLogger(WebResourceServlet.class); diff --git a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java new file mode 100644 index 0000000000..00d1f3aaf4 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java @@ -0,0 +1,151 @@ +package sonia.scm.web.i18n; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.legman.Subscribe; +import com.google.inject.Singleton; +import com.sun.org.apache.regexp.internal.RE; +import lombok.extern.slf4j.Slf4j; +import sonia.scm.NotFoundException; +import sonia.scm.SCMContext; +import sonia.scm.Stage; +import sonia.scm.boot.RestartEvent; +import sonia.scm.cache.Cache; +import sonia.scm.cache.CacheManager; +import sonia.scm.filter.WebElement; +import sonia.scm.plugin.PluginLoader; +import sonia.scm.plugin.UberClassLoader; + +import javax.inject.Inject; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Function; + + +/** + * Collect + */ +@Singleton +@WebElement(value = I18nServlet.PATTERN, regex = true) +@Slf4j +public class I18nServlet extends HttpServlet { + + public static final String PATH = "/locales"; + public static final String PLUGINS_JSON = "plugins.json"; + public static final String PATTERN = PATH + "/[a-z\\-A-Z]*/" + PLUGINS_JSON; + public static final String CACHE_NAME = "sonia.cache.plugins.translations"; + + private final UberClassLoader uberClassLoader; + private final Cache> cache; + private RE languagePathPostfix = new RE(".*(\\-[A-Z]+)/.*"); + private ObjectMapper objectMapper = new ObjectMapper(); + + + @Inject + public I18nServlet(PluginLoader pluginLoader, CacheManager cacheManager) { + this.uberClassLoader = (UberClassLoader) pluginLoader.getUberClassLoader(); + this.cache = cacheManager.getCache(CACHE_NAME); + } + + @Subscribe + public void handleRestartEvent(RestartEvent event) { + log.info("clear cache on restart event with reason {}", event.getReason()); + cache.clear(); + } + + public Map getCollectedJson(String path, + Function>> jsonFileProvider, + BiConsumer> createdJsonFileConsumer) throws NotFoundException { + return Optional.ofNullable(jsonFileProvider.apply(path) + .orElseGet(() -> { + Optional> createdFile = collectJsonFile(path); + createdFile.ifPresent(map -> createdJsonFileConsumer.accept(path, map)); + return createdFile.orElse(null); + } + )).orElseThrow(NotFoundException::new); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse response) { + try { + response.setContentType("application/json"); + PrintWriter out = response.getWriter(); + String path = req.getServletPath(); + Function>> jsonFileProvider = usedPath -> Optional.empty(); + BiConsumer> createdJsonFileConsumer = (usedPath, foundJsonMap) -> log.info("A json File is created from the path {}", usedPath); + if (SCMContext.getContext().getStage() == Stage.PRODUCTION) { + log.info("In Production Stage get the plugin translations from the cache"); + jsonFileProvider = usedPath -> Optional.ofNullable( + cache.get(usedPath)); + createdJsonFileConsumer = createdJsonFileConsumer + .andThen((usedPath, map) -> log.info("Put the created json File in the cache with the key {}", usedPath)) + .andThen(cache::put); + } + out.write(objectMapper.writeValueAsString(getCollectedJson(path, jsonFileProvider, createdJsonFileConsumer))); + } catch (IOException e) { + log.error("error on getting the translation of the plugins", e); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } catch (NotFoundException e) { + log.error("Plugin translations are not found", e); + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + } +// ScmEventBus.getInstance().post(new RestartEvent(I18nServlet.class,"einfach so")); + } + + /** + * Return a collected Json File as map with the given path from all plugins in the class path + * + * @param path the searched resource path + * @return a collected Json File as map with the given path from all plugins in the class path + */ + private Optional> collectJsonFile(String path) { + log.info("Collect plugin translations from path {} for every plugin", path); + HashMap result = null; + try { + Enumeration resources = uberClassLoader.getResources(path.replaceFirst("/", "")); + if (resources.hasMoreElements()) { + result = new HashMap<>(); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + result.putAll(mergeJSONs(objectMapper, url)); + } + } + } catch (IOException e) { + log.error("Error on loading sources from {}", path, e); + } + return Optional.ofNullable(result); + } + + private boolean hasLanguagePostfix(String path) { + return languagePathPostfix.match(path); + } + + /** + * remove the -DE from the path locales/de-DE/plugins + * + * @param servletPath + * @return + * @throws IOException + */ + private String removeLanguagePostfix(String servletPath) { + return servletPath.replace(languagePathPostfix.getParen(1), ""); + } + + // TODO simplify + private HashMap mergeJSONs(ObjectMapper objectMapper, URL url) throws IOException { + byte[] src = Files.readAllBytes(Paths.get(url.getPath())); + Map json = objectMapper.readValue(src, HashMap.class); + return objectMapper.readerForUpdating(json).readValue(src); + } +}