TransifexAPIController.java
/**
* Copyright 2014 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package org.genesys.server.mvc.transifex;
import java.io.IOException;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.genesys.server.component.aspect.AsAdmin;
import org.genesys.server.mvc.BaseController;
import org.genesys.server.service.ArticleService;
import org.genesys.server.service.ArticleTranslationService;
import org.genesys.transifex.client.TransifexException;
import org.genesys.transifex.client.TransifexService;
import org.genesys.transifex.client.TransifexService.TranslationMode;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Transifex web hook listener.
*
* @author matijaobreza
*
*/
@Controller
@RequestMapping(value = "/transifex")
public class TransifexAPIController extends BaseController {
// Recognize slug-shortName-targetId pattern
private static final Pattern scopedArticlePattern = Pattern.compile("^(.+)\\-(\\w+)\\-(\\d+)$", Pattern.CASE_INSENSITIVE);
@Autowired
private ArticleService articleService;
@Autowired(required = false)
private TransifexService transifexService;
@Value("${transifex.min.translated}")
private int transifexMinTranslated;
@Value("${transifex.hook.key}")
private Object transifexHookKey;
@Autowired
private ObjectMapper mapper;
/**
* Note: the hook key value should be set in preferences
*/
@AsAdmin
@RequestMapping(value = "/hook/{hookKey:.+}", method = RequestMethod.POST)
public @ResponseBody String webHookHandle(@PathVariable("hookKey") String hookKey, @RequestParam(value = "project") String projectSlug,
@RequestParam(value = "resource") String resource, @RequestParam(value = "language") String language,
@RequestParam(value = "translated") Integer translatedPercentage, Model model) {
if (!transifexHookKey.equals(hookKey)) {
LOG.error("Invalid key provided for Transifex callback hook: {}", hookKey);
throw new RuntimeException("I don't know you!");
}
if (!resource.startsWith("article-")) {
LOG.warn("Ignoring Transifex'd hook for {}", resource);
return "Ignored";
}
String slug = resource.split("-")[1];
String classPkShortName = null;
Long targetId = null;
LOG.warn("Transifex'd article slug={}", slug);
Matcher scopedMatcher = scopedArticlePattern.matcher(slug);
if (scopedMatcher.matches()) {
slug = scopedMatcher.group(1);
classPkShortName = scopedMatcher.group(2);
targetId = Long.parseLong(scopedMatcher.group(3));
LOG.info("Found scoped article slug={} classPk={} target={}", slug, classPkShortName, targetId);
} else {
LOG.debug("Not a scopedArticle {}", slug);
}
if (translatedPercentage != null && translatedPercentage >= transifexMinTranslated) {
// fetch updated resource from Transifex
try {
updateArticle(slug, classPkShortName, targetId, language);
} catch (TransifexException e) {
LOG.error(e.getMessage(), e);
throw new RuntimeException("Server error");
}
return "Thanks!";
} else {
return "Not sufficiently translated";
}
}
private void updateArticle(String slug, String classPkShortName, Long targetId, String language) throws TransifexException {
if (transifexService == null) {
LOG.error("translationService not available.");
throw new TransifexException("transifex.com service not available", null);
}
LOG.info("Fetching updated translation for article {} lang={}", slug, language);
Locale locale = new Locale(language);
LOG.warn("Locale: {}", locale);
String resourceBody = transifexService.getTranslatedResource("article-".concat(slug), locale, TranslationMode.ONLYTRANSLATED);
String title = null;
String body = null;
String summary = null;
try {
JsonNode jsonObject = mapper.readTree(resourceBody);
String content = jsonObject.get("content").asText();
Document doc = Jsoup.parse(content);
title = doc.title();
LOG.info("Title: {}", title);
if (content.contains("class=\"summary")) {
// 1st <div class="summary">...
summary = doc.body().child(0).html();
LOG.info("Summary: {}", summary);
// 2nd <div class="body">...
body = doc.body().child(1).html();
LOG.info("Body: {}", body);
} else {
// Old fashioned body-only approach
doc.body().html();
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
throw new TransifexException("Oops", e);
}
// Extract article from database we need (correct locale + do not use
// default (EN) language)
ArticleTranslationService.TranslatedArticle article;
if (targetId == null) {
article = articleService.getGlobalArticle(slug, locale);
} else {
article = articleService.getArticleBySlugLangTargetIdClassPk(slug, language, targetId, classPkShortName);
}
// TODO Enable when tested
// if (article == null) {
// // No article for selected locale
// article = contentService.createGlobalArticle(slug, locale, title,
// body);
// } else {
// // Update article for locale
// article = contentService.updateGlobalArticle(slug, locale, title,
// body);
// }
LOG.info("Updated translation for article {} lang={}", slug, language);
}
}