/**
* Copyright 2013 Tommi S.E. Laukkanen
*
* 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.groom.translation.service;
import org.apache.log4j.Logger;
import org.bubblecloud.ilves.model.Company;
import org.bubblecloud.ilves.model.Group;
import org.bubblecloud.ilves.model.User;
import org.bubblecloud.ilves.security.CompanyDao;
import org.bubblecloud.ilves.security.UserDao;
import org.bubblecloud.ilves.util.EmailUtil;
import org.bubblecloud.ilves.util.PropertiesUtil;
import org.groom.model.Repository;
import org.groom.review.dao.ReviewDao;
import org.groom.shell.Shell;
import org.groom.translation.model.Entry;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* Class which synchronizes bundles to database and back.
*
* @author Tommi S.E. Laukkanen
*/
public class TranslationSynchronizer {
/** The logger. */
private static final Logger LOGGER = Logger.getLogger(TranslationSynchronizer.class);
/**
* The entity manager.
*/
private final EntityManager entityManager;
/**
* Synchronization thread.
*/
private final Thread thread;
/**
* Shutdown requested.
*/
private boolean shutdown = false;
private static long lastTimeMillis;
public static void startSynchronize() {
TranslationSynchronizer.lastTimeMillis = 0;
}
/**
* Constructor which starts synchronizer.
*
* @param entityManager the entity manager.
*/
public TranslationSynchronizer(final EntityManager entityManager) {
this.entityManager = entityManager;
final long synchronizePeriodMillis = Long.parseLong(PropertiesUtil.getProperty("groom",
"synchronize-period-millis"));
thread = new Thread(new Runnable() {
@Override
public void run() {
lastTimeMillis = System.currentTimeMillis();
lastTimeMillis = lastTimeMillis - lastTimeMillis % synchronizePeriodMillis;
while (!shutdown) {
synchronize();
while (System.currentTimeMillis() < lastTimeMillis + synchronizePeriodMillis) {
try {
Thread.sleep(100);
} catch (final InterruptedException e) {
LOGGER.debug(e);
if (shutdown) {
return;
}
}
}
lastTimeMillis = System.currentTimeMillis();
lastTimeMillis = lastTimeMillis - lastTimeMillis % synchronizePeriodMillis;
}
}
});
thread.start();
}
/**
* Synchronizes bundles and database.
*/
private void synchronize() {
entityManager.clear();
final List<Company> companies = CompanyDao.getCompanies(entityManager);
for (final Company company : companies) {
final List<Repository> repositories = ReviewDao.getRepositories(entityManager, company);
for (final Repository repository : repositories) {
synchronizeRepository(repository);
}
}
}
/**
* Synchronizes bundles and database for given repository.
*/
private void synchronizeRepository(final Repository repository) {
final String absoluteRepositoryPathPrefix = PropertiesUtil.getProperty("groom", "repository-path") + "/translation";
final String relativeRepositoryPath = "translation/" + repository.getPath();
if (!new File(absoluteRepositoryPathPrefix).exists()) {
new File(absoluteRepositoryPathPrefix).mkdir();
LOGGER.info(Shell.execute("git clone " + repository.getUrl() + " " + repository.getPath(), "translation"));
}
LOGGER.info(Shell.execute("git fetch", relativeRepositoryPath));
LOGGER.info(Shell.execute("git reset origin/master --hard", relativeRepositoryPath));
final String absoluteRepositoryPath = absoluteRepositoryPathPrefix + "/" + repository.getPath();
final String bundleCharacterSet = PropertiesUtil.getProperty("groom", "bundle-character-set");
final String[] prefixes = repository.getBundlePrefixes().split(",");
final Company company = repository.getOwner();
for (final String prefix : prefixes) {
final File baseBundle = new File(absoluteRepositoryPath + "/" + prefix + ".properties");
if (!baseBundle.exists()) {
LOGGER.info("Base bundle does not exist: " + baseBundle.getAbsolutePath());
continue;
}
LOGGER.info("Base bundle exists: " + baseBundle.getAbsolutePath());
String baseName = baseBundle.getName().substring(0, baseBundle.getName().length() - 11);
if (baseName.indexOf('_') >= 0) {
baseName = baseName.substring(0, baseName.indexOf('_'));
}
final File bundleDirectory = baseBundle.getParentFile();
final String bundleDirectoryPath = bundleDirectory.getAbsolutePath();
LOGGER.info("Basename: " + baseName);
LOGGER.info("Path: " + bundleDirectoryPath);
final Set<Object> keys;
final Properties baseBundleProperties;
try {
baseBundleProperties = new Properties();
final FileInputStream baseBundleInputStream = new FileInputStream(baseBundle);
baseBundleProperties.load(new InputStreamReader(baseBundleInputStream, bundleCharacterSet));
keys = baseBundleProperties.keySet();
baseBundleInputStream.close();
} catch (Exception e) {
LOGGER.error("Error reading bundle: " + baseName, e);
continue;
}
final Map<String, List<String>> missingKeys = new HashMap<String, List<String>>();
final SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss");
for (final File candidate : bundleDirectory.listFiles()) {
if (candidate.getName().startsWith(baseName) && candidate.getName().endsWith(".properties")) {
final String name = candidate.getName().split("\\.")[0];
final String[] parts = name.split("_");
String candidateBaseName = parts[0];
if (candidateBaseName.equals(baseName)) {
String language = "";
String country = "";
if (parts.length > 1) {
language = parts [1];
if (parts.length > 2) {
country = parts[2];
}
}
LOGGER.info("Bundle basename: '" + candidateBaseName
+ "' language: '" + language + "' country: '" + country + "'");
entityManager.getTransaction().begin();
try {
final Properties properties = new Properties();
final FileInputStream bundleInputStream = new FileInputStream(candidate);
properties.load(new InputStreamReader(bundleInputStream, bundleCharacterSet));
bundleInputStream.close();
final TypedQuery<Entry> query = entityManager.createQuery("select e from Entry as e where " +
"e.path=:path and e.basename=:basename and " +
"e.language=:language and e.country=:country order by e.key", Entry.class);
query.setParameter("path", bundleDirectoryPath);
query.setParameter("basename", baseName);
query.setParameter("language", language);
query.setParameter("country", country);
final List<Entry> entries = query.getResultList();
final Set<String> existingKeys = new HashSet<String>();
for (final Entry entry : entries) {
if (keys.contains(entry.getKey())) {
if (entry.getValue().length() == 0 && properties.containsKey(entry.getKey()) &&
((String) properties.get(entry.getKey())).length() > 0) {
entry.setValue((String) properties.get(entry.getKey()));
entityManager.persist(entry);
}
}
existingKeys.add(entry.getKey());
}
for (final Object obj : keys) {
final String key = (String) obj;
final String value;
if (properties.containsKey(key)) {
value = (String) properties.get(key);
} else {
value = "";
}
if (!existingKeys.contains(key)) {
final Entry entry = new Entry();
entry.setOwner(company);
entry.setRepository(repository);
entry.setPath(bundleDirectoryPath);
entry.setBasename(baseName);
entry.setLanguage(language);
entry.setCountry(country);
entry.setKey(key);
entry.setValue(value);
entry.setCreated(new Date());
entry.setModified(entry.getCreated());
entityManager.persist(entry);
final String locale = entry.getLanguage() + "_" + entry.getCountry();
if (!missingKeys.containsKey(locale)) {
missingKeys.put(locale, new ArrayList<String>());
}
missingKeys.get(locale).add(entry.getKey());
}
}
entityManager.getTransaction().commit();
final FileOutputStream fileOutputStream = new FileOutputStream(candidate, false);
final OutputStreamWriter writer = new OutputStreamWriter(fileOutputStream,
bundleCharacterSet);
final PrintWriter printWriter = new PrintWriter(writer);
for (final Entry entry : query.getResultList()) {
if (entry.getDeleted() == null) {
printWriter.print("# Modified: ");
printWriter.print(format.format(entry.getModified()));
if (entry.getAuthor() != null) {
printWriter.print(" Author: ");
printWriter.print(entry.getAuthor());
}
printWriter.println();
printWriter.print(entry.getKey());
printWriter.print("=");
final String value = entry.getValue().replace("\n", "\\n");
printWriter.println(value);
}
}
printWriter.flush();
printWriter.close();
fileOutputStream.close();
} catch (Exception e) {
if (entityManager.getTransaction().isActive()) {
entityManager.getTransaction().rollback();
}
LOGGER.error("Error reading bundle: " + baseName, e);
continue;
}
}
}
}
for (final String locale : missingKeys.keySet()) {
final List<String> keySet = missingKeys.get(locale);
final String subject = "Please translate " + locale;
String content = "Missing keys are: ";
for (final String key : keySet) {
content += key + "\n";
}
final Group group = UserDao.getGroup(entityManager, company, locale);
if (group != null) {
final List<User> users = UserDao.getGroupMembers(entityManager, company, group);
for (final User user : users) {
LOGGER.info("Sending translation request to " + user.getEmailAddress() + " for " + locale +
" keys " + keySet);
EmailUtil.send(user.getEmailAddress(), company.getSupportEmailAddress(), subject, content);
}
}
}
}
LOGGER.info(Shell.execute("git commit -a -m 'Translations.'", relativeRepositoryPath));
LOGGER.info(Shell.execute("git push origin master", relativeRepositoryPath));
}
/**
* Executes requested shell command.
*
* @param cmd the shell command to execute
*/
private void executeShellCommand(final String cmd) {
LOGGER.debug("Executing shell command: " + cmd);
try {
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(new String[] {"/bin/sh", "-c", cmd});
process.waitFor();
String line;
BufferedReader error = new BufferedReader(new InputStreamReader(process.getErrorStream()));
while ((line = error.readLine()) != null){
LOGGER.error(line);
}
error.close();
BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((line = input.readLine()) != null){
LOGGER.info(line);
}
input.close();
LOGGER.debug("Executed shell command: " + cmd);
} catch (final Throwable t) {
LOGGER.error("Error executing shell command: " + cmd, t);
}
}
/**
* Shutdown.
*/
public final void shutdown() {
shutdown = true;
try {
thread.interrupt();
thread.join();
} catch (final InterruptedException e) {
LOGGER.debug(e);
}
}
}