TransifexServiceImpl.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.transifex.client;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Locale;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;

/**
 * Java implementation for Transifex API 2.
 *
 * @author alexandr
 * @author matijaobreza
 */
@Service
public class TransifexServiceImpl implements TransifexService, InitializingBean {

	/** The Constant LOG. */
	private static final Logger LOG = Logger
			.getLogger(TransifexServiceImpl.class);

	/** The Constant TRANSIFEX_API_URL. */
	private static final String TRANSIFEX_API_URL = "https://www.transifex.com/api/2";

	/** Transifex project slug */
	@Value("${transifex.project}")
	private String projectSlug;

	/** The trasifex user name. */
	@Value("${transifex.username}")
	private String trasifexUserName;

	/** The transifex passord. */
	@Value("${transifex.password}")
	private String transifexPassord;

	/** The content template. */
	@Value("${transifex.content.template}")
	private String contentTemplate;

	/** The Spring REST template. */
	private RestTemplate template = new RestTemplate();

	/** The transifex project url. */
	private String transifexProjectUrl;

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		transifexProjectUrl = TRANSIFEX_API_URL.concat("/project/").concat(
				projectSlug);
		LOG.info("Transifex project URL: " + transifexProjectUrl);
//		ensureTransifexCredentials();
	}

	/**
	 * Ensure transifex credentials.
	 *
	 * @throws TransifexException
	 *             the transifex exception
	 */
	@Override
	public void testCredentials() throws TransifexException {
		HttpHeaders headers = basicAuthentication();
		HttpEntity<String> request = new HttpEntity<>(headers);
		try {
			ResponseEntity<String> response = template.exchange(
					transifexProjectUrl + "/?details", HttpMethod.GET, request,
					String.class);

			// response.getBody() // contains JSON information about the project

			if (LOG.isInfoEnabled()) {
				LOG.info("Transifex connection okay! HTTP status code="
						+ response.getStatusCode());
			}
		} catch (HttpClientErrorException e) {
			// Fail here to stop initializing the context!
			throw new TransifexException(
					"Transifex credentials are not valid.", e);
		}
	}

	/**
	 * Transifex requires Basic HTTP authentication! This method adds Basic
	 * authentication.
	 *
	 * @return the http headers with Auhtorization header
	 */
	protected HttpHeaders basicAuthentication() {
		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;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.genesys.transifex.client.TransifexService#resourceExists(java.lang
	 * .String)
	 */
	@Override
	public boolean resourceExists(String slug) {
		if (LOG.isDebugEnabled()) {
			LOG.debug("Checking for resource " + slug);
		}

		ResponseEntity<String> response;

		HttpHeaders headers = basicAuthentication();
		HttpEntity<String> request = new HttpEntity<>(headers);
		try {
			response = template.exchange(transifexProjectUrl
					+ "/resource/{slug}", HttpMethod.GET, request,
					String.class, slug);

			if (LOG.isDebugEnabled()) {
				LOG.debug(response.getStatusCode() + " " + response.getBody());
			}
		} catch (HttpClientErrorException e) {
			return false;
		}

		return response.getStatusCode().value() == HttpStatus.OK.value();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.genesys.transifex.client.TransifexService#deleteResource(java.lang
	 * .String)
	 */
	@Override
	public boolean deleteResource(String slug) throws TransifexException {
		HttpHeaders headers = basicAuthentication();
		HttpEntity<String> request = new HttpEntity<>(headers);
		try {
			ResponseEntity<String> response = template.exchange(
					transifexProjectUrl + "/resource/{slug}",
					HttpMethod.DELETE, request, String.class, slug);
			if (response.getStatusCode().value() == 204) {
				return true;
			}
			return false;
		} catch (HttpClientErrorException e) {
			throw new TransifexException(e.getMessage(), e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.genesys.transifex.client.TransifexService#getTranslatedResource(
	 * java.lang.String, java.util.Locale)
	 */
	@Override
	public String getTranslatedResource(String slug, Locale locale, TranslationMode mode)
			throws TransifexException {
		HttpHeaders headers = basicAuthentication();

		HttpEntity<String> request = new HttpEntity<>(headers);
		try {
			ResponseEntity<String> response = template.exchange(
					transifexProjectUrl
							+ "/resource/{slug}/translation/{language}?mode={mode}",
					HttpMethod.GET, request, String.class, slug,
					locale.toLanguageTag(), mode.toString().toLowerCase());

			// FIXME Check response status. Proceed only on HTTP OK response.
			if (LOG.isDebugEnabled()) {
				LOG.debug("Response code=" + response.getStatusCode());
			}

			return response.getBody();
		} catch (HttpClientErrorException | ResourceAccessException e) {
			throw new TransifexException("Error fetching translated resource",
					e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.genesys.transifex.client.TransifexService#downloadTranslatedResource
	 * (java.lang.String, java.util.Locale)
	 */
	@Override
	public String downloadTranslatedResource(String slug, Locale locale, TranslationMode mode)
			throws TransifexException {
		HttpHeaders headers = basicAuthentication();

		HttpEntity<byte[]> request = new HttpEntity<>(headers);
		try {
			ResponseEntity<byte[]> response = template.exchange(
					transifexProjectUrl
							+ "/resource/{slug}/translation/{language}?mode={mode}&file",
					HttpMethod.GET, request, byte[].class, slug,
					locale.toLanguageTag(), mode.toString().toLowerCase());

			// FIXME Check response status. Proceed only on HTTP OK response.
			if (LOG.isDebugEnabled()) {
				LOG.debug("Response code=" + response.getStatusCode());
				LOG.debug("Response ct="
						+ response.getHeaders().getContentType());
			}

			// Do it with byte[] so we can enforce UTF8
			return new String(response.getBody(), Charset.forName("UTF-8"));

		} catch (HttpClientErrorException e) {
			throw new TransifexException("Error fetching translated resource",
					e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.genesys.transifex.client.TransifexService#createXhtmlResource(java
	 * .lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	public void createXhtmlResource(String slug, String title, String content)
			throws IOException {
		// Make the authentication for Transifex
		HttpHeaders headers = basicAuthentication();
		// We will request like MULTIPART_FORM_DATA
		headers.setContentType(MediaType.MULTIPART_FORM_DATA);

		// Create Multi value map with all necessary information for request
		MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
		map.add("slug", slug);
		map.add("name", title);
		map.add("i18n_type", "XHTML");

		File tempFile = File.createTempFile(slug, ".xhtml");
		// default FileWriter support default encoding only
		try (BufferedWriter writer = new BufferedWriter(
				new FileWriter(tempFile))) {

			LOG.debug(content);

			// This is template our xhtml
			String xhtmlContent = String
					.format(contentTemplate, title, content);

			IOUtils.write(xhtmlContent, 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);

			// Send our post request(with .xhtml file) to Transifex
			ResponseEntity<Object> response = template.postForEntity(
					transifexProjectUrl + "/resources/", request, Object.class);
			if (LOG.isDebugEnabled()) {
				// 201 CREATED is returned by Transifex API 2
				LOG.debug("Response: " + response.getStatusCode());
			}

		} catch (HttpClientErrorException e) {
			LOG.error(e.getMessage());
			LOG.error(e.getResponseBodyAsString());
			throw e;
		} finally {
			tempFile.delete();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.genesys.transifex.client.TransifexService#updateXhtmlResource(java
	 * .lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	public void updateXhtmlResource(String slug, String title, String content)
			throws IOException {

		if (!resourceExists(slug)) {
			// POST the resource
			createXhtmlResource(slug, title, content);
			return;
		}

		LOG.info("Updating Transifex resource " + slug);

		// Make the authentication for Transifex
		HttpHeaders headers = basicAuthentication();
		// We will request like MULTIPART_FORM_DATA
		headers.setContentType(MediaType.MULTIPART_FORM_DATA);

		// Create Multi value map with all necessary information for request
		MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
		// map.add("slug", slug);
		// map.add("name", title);
		// map.add("i18n_type", "XHTML");

		File tempFile = File.createTempFile(slug, ".xhtml");
		// default FileWriter support default encoding only
		try (BufferedWriter writer = new BufferedWriter(
				new FileWriter(tempFile))) {

			LOG.debug(content);

			// This is template our xhtml
			String xhtmlContent = String
					.format(contentTemplate, title, content);

			IOUtils.write(xhtmlContent, 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);

			// Send our post request(with .xhtml file) to Transifex
			template.put(transifexProjectUrl + "/resource/{slug}/content/",
					request, slug);

		} catch (HttpClientErrorException e) {
			LOG.error(e.getMessage());
			LOG.error(e.getResponseBodyAsString());
			throw e;
		} finally {
			tempFile.delete();
		}
	}

	/**
	 * Creates the properties xml resource.
	 *
	 * @param slug
	 *            the slug
	 * @param title
	 *            the title
	 * @param file
	 *            the file
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public void createPropertiesXmlResource(String slug, String title, File file)
			throws IOException {
		// Make the authentication for Transifex
		HttpHeaders headers = basicAuthentication();
		// We will request like MULTIPART_FORM_DATA
		headers.setContentType(MediaType.MULTIPART_FORM_DATA);

		// Create Multi value map with all necessary information for request
		MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
		map.add("slug", slug);
		map.add("name", title);
		map.add("i18n_type", "PROPERTIESXML");

		try {
			Resource resource = new FileSystemResource(file);
			map.add("content", resource);

			// Create our request entity
			HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(
					map, headers);

			// Send our post reques to Transifex
			ResponseEntity<Object> response = template.postForEntity(
					transifexProjectUrl + "/resources/", request, Object.class);
			if (LOG.isDebugEnabled()) {
				// 201 CREATED is returned by Transifex API 2
				LOG.debug("Response: " + response.getStatusCode());
			}

		} catch (HttpClientErrorException e) {
			LOG.error(e.getMessage());
			LOG.error(e.getResponseBodyAsString());
			throw e;
		} finally {

		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.genesys.transifex.client.TransifexService#updatePropertiesXmlResource
	 * (java.lang.String, java.lang.String, java.io.File)
	 */
	@Override
	public void updatePropertiesXmlResource(String slug, String title, File file)
			throws IOException {

		if (!resourceExists(slug)) {
			// POST the resource
			createPropertiesXmlResource(slug, title, file);
			return;
		}

		LOG.info("Updating Transifex resource " + slug);

		// Make the authentication for Transifex
		HttpHeaders headers = basicAuthentication();
		// We will request like MULTIPART_FORM_DATA
		headers.setContentType(MediaType.MULTIPART_FORM_DATA);

		// Create Multi value map with all necessary information for request
		MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();

		try {

			Resource resource = new FileSystemResource(file);
			map.add("content", resource);

			// Create our request entity
			HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(
					map, headers);

			// Send our post request(with .xhtml file) to Transifex
			template.put(transifexProjectUrl + "/resource/{slug}/content/",
					request, slug);

		} catch (HttpClientErrorException e) {
			LOG.error(e.getMessage());
			LOG.error(e.getResponseBodyAsString());
			throw e;
		} finally {

		}
	}
}