Commit f295095d authored by Alexander Basov's avatar Alexander Basov Committed by Matija Obreza
Browse files

Transifex integration for review

parent f1cf8d1b
......@@ -21,6 +21,7 @@ import org.hibernate.annotations.Type;
import javax.persistence.*;
import java.util.Calendar;
import java.util.List;
@Entity
@Table(name = "article")
......@@ -53,7 +54,8 @@ public class Article extends AuditedModel {
@Column(length = 100000)
private String body;
private boolean transifexed = true;
@OneToMany(mappedBy = "article",fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<TransifexTranslation> transifexTranslations;
@Temporal(TemporalType.TIMESTAMP)
private Calendar postDate;
......@@ -114,11 +116,12 @@ public class Article extends AuditedModel {
this.lang = lang;
}
public boolean isTransifexed() {
return transifexed;
public List<TransifexTranslation> getTranslations() {
return transifexTranslations;
}
public void setTransifexed(boolean transifexed) {
this.transifexed = transifexed;
public void setTranslations(List<TransifexTranslation> translations) {
this.transifexTranslations = translations;
}
}
package org.genesys2.server.model.impl;
import org.genesys2.server.model.BusinessModel;
import javax.persistence.*;
@Entity
@Table(name = "transifextranslation")
public class TransifexTranslation extends BusinessModel{
private static final long serialVersionUID = 1L;
@Column
private String translatedLang;
@Column
private boolean transifexed;
@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "article_id", nullable = false)
private Article article;
public String getTranslatedLang() {
return translatedLang;
}
public void setTranslatedLang(String translatedLang) {
this.translatedLang = translatedLang;
}
public boolean isTransifexed() {
return transifexed;
}
public void setTransifexed(boolean transifexed) {
this.transifexed = transifexed;
}
public Article getArticle() {
return article;
}
public void setArticle(Article article) {
this.article = article;
}
}
package org.genesys2.server.persistence.domain;
import org.genesys2.server.model.impl.TransifexTranslation;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface TransifextranslationRepository extends JpaRepository<TransifexTranslation, Long>{
@Query("select tt from TransifexTranslation tt join tt.article a where a.id = :articleId")
List<TransifexTranslation> findByArticleId(@Param("articleId")Long articleId);
}
package org.genesys2.server.service;
import org.genesys2.server.model.impl.TransifexTranslation;
import java.util.List;
public interface TransifexTranslationService {
TransifexTranslation getTransifexTranslation(Long id);
void save(String articleName, TransifexTranslation transifexTranslations);
void deleteTransifexTranslation(Long id);
List<TransifexTranslation> findAll();
List<TransifexTranslation> findAllByArticleId(Long articleId);
}
package org.genesys2.server.service.impl;
import org.genesys2.server.model.impl.Article;
import org.genesys2.server.model.impl.ClassPK;
import org.genesys2.server.model.impl.TransifexTranslation;
import org.genesys2.server.persistence.domain.ArticleRepository;
import org.genesys2.server.persistence.domain.ClassPKRepository;
import org.genesys2.server.persistence.domain.TransifextranslationRepository;
import org.genesys2.server.service.TransifexTranslationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional
public class TransifexTranslationServiceImpl implements TransifexTranslationService {
@Autowired
private TransifextranslationRepository transifextranslationRepository;
@Autowired
private ArticleRepository articleRepository;
@Autowired
private ClassPKRepository classPKRepository;
@Override
public TransifexTranslation getTransifexTranslation(Long id) {
return transifextranslationRepository.getOne(id);
}
@Override
public void save(String articleName, TransifexTranslation transifexTranslations) {
ClassPK articleClassPk = classPKRepository.findByClassName(Article.class.getName());
Article article = articleRepository.findByClassPkAndTargetIdAndSlugAndLang(articleClassPk,
null, articleName, LocaleContextHolder.getLocale().getLanguage());
if (article != null) {
transifexTranslations.setArticle(article);
transifextranslationRepository.save(transifexTranslations);
}
}
@Override
public void deleteTransifexTranslation(Long id) {
transifextranslationRepository.delete(id);
}
@Override
public List<TransifexTranslation> findAll() {
return transifextranslationRepository.findAll();
}
@Override
public List<TransifexTranslation> findAllByArticleId(Long articleId) {
return transifextranslationRepository.findByArticleId(articleId);
}
}
package org.genesys2.server.servlet.controller.transifex;
import org.apache.commons.codec.binary.Base64;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.io.HTMLWriter;
import org.dom4j.io.OutputFormat;
import org.apache.commons.io.IOUtils;
import org.genesys2.server.model.impl.Article;
import org.genesys2.server.model.impl.TransifexTranslation;
import org.genesys2.server.service.ContentService;
import org.genesys2.server.service.TransifexTranslationService;
import org.genesys2.server.servlet.controller.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
......@@ -25,9 +23,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.*;
@Controller
@RequestMapping(value = "/transifex")
......@@ -36,6 +32,9 @@ public class TransifexAPIController extends BaseController{
@Autowired
private ContentService contentService;
@Autowired
private TransifexTranslationService translationService;
@Value("${transifex.project}")
private String projectSlug;
......@@ -45,23 +44,24 @@ public class TransifexAPIController extends BaseController{
@Value("${transifex.password}")
private String transifexPassord;
@Value("${transifex.min.translated}")
private String minTranslatedValue;
@Value("${transifex.base.api.url}")
private String baseApiURL;
private RestTemplate template;
private HttpHeaders headers;
@Value("${transifex.content.template}")
private String contentTemplate;
@Value("${transifex.min.translated}")
private int transifexMinTranslated;
private RestTemplate template = new RestTemplate();
@RequestMapping(value = "/deleteContent/{slug}")
public String deleteResource(@PathVariable String slug, Model model) {
//make authentication for Transifex
basicAuthentication();
HttpHeaders headers = basicAuthentication();
Article article = contentService.getGlobalArticle(slug, getLocale());
//
if (resourceExists(article)) {
StringBuilder url = new StringBuilder();
url.append("project/");
......@@ -74,37 +74,56 @@ public class TransifexAPIController extends BaseController{
if(response.getStatusCode().value() == 204){
model.addAttribute("result", "deleted ok");
}
}else{
model.addAttribute("result", "not exists");
}
model.addAttribute("title", article.getTitle());
model.addAttribute("article", article);
return "/content/article-edit";
}
@RequestMapping(value = "/hooks", method = RequestMethod.POST)
/**
* Use "magic" SHA1 hash value just for some security purposes
*
* Note: the very same value should be in Transifex preferences
*
* @see classpath:spring/spring-security.xml
*/
@RequestMapping(value = "/hooks/40874cca86ca396169a5f4e6ebf6e4bf7199c4e7", method = RequestMethod.POST)
public void webHookHandle(@RequestParam(value = "project") String projectSlug,
@RequestParam(value = "resource") String resource,
@RequestParam(value = "language") String language,
@RequestParam(value = "translated") int translated){
System.out.println(projectSlug+" done \n");
@RequestParam(value = "translated") int translatedPercentage, Model model){
//TODO may be revisit this
//currently we do this due to nature of Transifex hook
String articleName = resource.split("-")[1];
if(translatedPercentage == transifexMinTranslated){
TransifexTranslation translation = new TransifexTranslation();
translation.setTransifexed(true);
translation.setTranslatedLang(language);
translationService.save(articleName, translation);
}
}
@RequestMapping(value = "/translate/{slug}/{lang}", method = RequestMethod.GET)
public String translateArticles(@PathVariable String lang, @PathVariable String slug, Model model) throws DocumentException {
public String translateArticles(@PathVariable String lang, @PathVariable String slug, Model model) {
//make authentication for Transifex
basicAuthentication();
HttpHeaders headers = basicAuthentication();
//Extract article from database we need
Article article = contentService.getGlobalArticle(slug, getLocale());
StringBuilder url = new StringBuilder();
url.append("project/");
url.append(projectSlug);
url.append("/resource/");
url.append("article-");
url.append(slug);
url.append("/translation/");
url.append(lang);
url.append("/?file");
StringBuilder url = new StringBuilder()
.append("project/")
.append(projectSlug)
.append("/resource/")
.append("article-")
.append(slug)
.append("/translation/")
.append(lang)
.append("/?file");
HttpEntity<String> request = new HttpEntity<>(headers);
......@@ -113,10 +132,7 @@ public class TransifexAPIController extends BaseController{
String title = response.getBody().split("<title>")[1].split("</title>")[0];
String body = response.getBody().split("<body>")[1].split("</body>")[0];
article.setTitle(title);
article.setBody(body);
contentService.updateArticle(article.getId(), article.getSlug(), article.getTitle(), article.getBody());
contentService.updateArticle(article.getId(), article.getSlug(), title, body);
model.addAttribute("title", article.getTitle());
model.addAttribute("article", article);
......@@ -125,93 +141,78 @@ public class TransifexAPIController extends BaseController{
}
@RequestMapping(value = "/postContent/{slug}")
public String postResourceToTransifex(@PathVariable String slug, Model model) throws IOException, DocumentException {
//We want to sent .xhtml file to transifex, for this we use dom4j
public String postResourceToTransifex(@PathVariable String slug, Model model) throws IOException {
//Make the authentication for Transifex
basicAuthentication();
HttpHeaders headers = basicAuthentication();
//Extract article from database we need
Article article = contentService.getGlobalArticle(slug, getLocale(), false);
//Create temporary file articleSlug.xhtml
String filePath = "/home/alexander/Downloads/"+article.getSlug()+".xhtml";
File xhtmlFile = new File(filePath);
xhtmlFile.createNewFile();
//Create dom4j document witch will be containing xhtml content
Document xhtmlDocument;
//Define xhtml output format for HTMLWriter
OutputFormat outputFormat = OutputFormat.createPrettyPrint();
outputFormat.setXHTML(true);
HTMLWriter writer = new HTMLWriter(new FileWriter(xhtmlFile), outputFormat);
if (!resourceExists(article)) {
//We will request like MULTIPART_FORM_DATA
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
//This is template our xhtml
String content = "<html xmlns=\"http://www.w3.org/1999/xhtml\"> \n" +
" <head> \n" +
"<meta http-equiv=\"CONTENT-TYPE\" content=\"text/html; charset=utf­8\"/> \n" +
"<title>"+article.getTitle()+"</title> \n" +
" </head> \n" +
" <body>"+article.getBody()+"</body> \n" +
"</html>";
//Parse String content and write it into articleSlug.xhtml file
xhtmlDocument = DocumentHelper.parseText(content);
writer.write(xhtmlDocument);
writer.flush();
//Create Multi value map with all necessary information for request
String content = String.format(contentTemplate, article.getTitle(), article.getBody());
//Create Multi
// value map with all necessary information for request
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("slug", "article-"+slug);
map.add("name", article.getTitle());
map.add("i18n_type", "XHTML");
Resource resource = new FileSystemResource(filePath);
map.add("content", resource);
//Create our request entity
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(map, headers);
model.addAttribute("title", article.getTitle());
model.addAttribute("article", article);
File tempFile = File.createTempFile(slug, ".xhtml");
//default FileWriter support default encoding only
try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))){
StringBuilder url = new StringBuilder();
url.append("project/");
url.append(projectSlug);
url.append("/resources/");
IOUtils.write(content, writer);
writer.flush();
Resource resource = new FileSystemResource(tempFile);
map.add("content", resource);
//Create our request entity
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(map, headers);
StringBuilder url = new StringBuilder();
url.append("project/");
url.append(projectSlug);
url.append("/resources/");
try {
//Send our post request(with .xhtml file) to Transifex
template.postForLocation(baseApiURL + url, request);
model.addAttribute("responseFromTransifex", "Resource added");
} catch (HttpClientErrorException e) {
model.addAttribute("responseFromTransifex", "fail");
}
//After all operations, temporary .xhtml file is deleted
xhtmlFile.delete();
}else{
model.addAttribute("responseFromTransifex", "already exists");
}
model.addAttribute("title", article.getTitle());
model.addAttribute("article", article);
return "/content/article-edit";
}
//Transifex requires Basic HTTP authentication!
//This method make authentication for Transifex
public void basicAuthentication() {
public HttpHeaders basicAuthentication() {
template = new RestTemplate();
headers = new HttpHeaders();
HttpHeaders headers = new HttpHeaders();
String trasifexCreds = trasifexUserName + ":" + transifexPassord;
byte[] transifexCredsBytes = trasifexCreds.getBytes();
byte[] base64CredsBytes = Base64.encodeBase64(transifexCredsBytes);
String base64Creds = new String(base64CredsBytes);
headers.add("Authorization", "Basic " + base64Creds);
return headers;
}
//This method verify if article already exists in Transifex
public boolean resourceExists(Article resource) {
//make authentication for Transifex
basicAuthentication();
HttpHeaders headers = basicAuthentication();
StringBuilder url = new StringBuilder();
url.append("project/");
url.append(projectSlug);
......@@ -228,7 +229,7 @@ public class TransifexAPIController extends BaseController{
return false;
}
return response.getStatusCode().value() == 200;
return response.getStatusCode().value() == HttpStatus.OK.value();
}
}
......@@ -16,8 +16,6 @@
package org.genesys2.spring.config;
import java.util.Properties;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
......@@ -29,6 +27,8 @@ import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.util.Properties;
@EnableJpaRepositories(basePackages = { "org.genesys2.server.persistence.domain", "org.genesys2.server.persistence.acl" }, entityManagerFactoryRef = "entityManagerFactory", transactionManagerRef = "transactionManager", repositoryImplementationPostfix = "CustomImpl")
// @EnableJpaAuditing(auditorAwareRef = "auditorAware")
@EnableTransactionManagement
......
......@@ -31,6 +31,7 @@ public class SpringProperties {
final PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer();
// Need to ignore "genesys.properties" if not found
propertyPlaceholderConfigurer.setIgnoreResourceNotFound(true);
propertyPlaceholderConfigurer.setFileEncoding("utf-8");
propertyPlaceholderConfigurer.setLocations(new Resource[] {
new ClassPathResource("application.properties"),
......
......@@ -47,13 +47,23 @@
<!-- And other stuff -->
<sec:http pattern="/webapi/**" security="none" create-session="stateless" />
<!--Ignore security for Transifex hook-->
<!--Use "magic" SHA1 hash value just for some security purposes-->
<!--see org.genesys2.server.servlet.controller.transifex.TransifexAPIController#webHookHandle-->
<!--* Note: the very same value should be in Transifex preferences-->
<sec:http pattern="/transifex/hooks/40874cca86ca396169a5f4e6ebf6e4bf7199c4e7" security="none" create-session="stateless" />
<!-- Closed page and Authentication filter -->
<sec:http auto-config="true" use-expressions="true">
<!-- <intercept-url pattern="/data/**" access="isAuthenticated()" /> -->
<sec:intercept-url pattern="/admin/**" access="hasRole('ADMINISTRATOR')" />
<sec:intercept-url pattern="/profile**" access="isAuthenticated()" />
<sec:intercept-url pattern="/oauth/authorize" access="isAuthenticated()" />
<!--secure transifex URLs-->
<sec:intercept-url pattern="/transifex/**" access="isAuthenticated()" />
<!--Override default login and logout pages -->
<sec:form-login login-page="/login" authentication-failure-url="/login?error=1" login-processing-url="/login-attempt" default-target-url="/" always-use-default-target="false" />
<sec:session-management session-fixation-protection="migrateSession" />
......
......@@ -130,4 +130,12 @@ transifex.project=genesys-dev
transifex.username=Alexandr19011990
transifex.password=Alexandr19011990
transifex.min.translated=80
transifex.base.api.url =https://www.transifex.com/api/2/
transifex.base.api.url=https://www.transifex.com/api/2/
#TODO perhaps, Velocity template?
transifex.content.template=<html xmlns="http://www.w3.org/1999/xhtml">\
<head>\
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>\
<title>%s</title>\
</head>\
<body>%s</body>\
</html>
......@@ -21,11 +21,11 @@
<a href="<c:url value="/transifex/deleteContent/${article.slug}"/>" class="btn btn-default">Delete from
Transifex</a>
</div>
<div>${resource}</div>
<div class="form-group">
<div class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><c:out value="Transifex's translate" /> <b class="caret"></b></a>
<ul class="dropdown-menu" role="menu">
<ul class="dropdown-menu" role="menu" id="transifiex_langs">
<li><a href="${pageContext.request.contextPath}/transifex/translate/${article.slug}/${"en"}">English</a></li>
<li><a href="${pageContext.request.contextPath}/transifex/translate/${article.slug}/${"es"}">Spanish</a></li>
<li><a href="${pageContext.request.contextPath}/transifex/translate/${article.slug}/${"ar"}">Arabic</a></li>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment