com.myGengo.alfresco.translate.MyGengoTranslationServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.myGengo.alfresco.translate.MyGengoTranslationServiceImpl.java

Source

/**
 * Copyright (C) 2012 fme AG.
 *
 * This file is part of the myGengo Alfresco integration implmented by fme AG (http://alfresco.fme.de).
 *
 * 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 com.myGengo.alfresco.translate;

import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.imageio.ImageIO;

import org.alfresco.model.ContentModel;
import org.alfresco.model.ForumModel;
import org.alfresco.repo.batch.BatchProcessor;
import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker;
import org.alfresco.repo.lock.JobLockService;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.GUID;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONStringer;

import com.google.common.collect.Lists;
import com.google.gdata.util.common.html.HtmlToText;
import com.myGengo.alfresco.account.MyGengoAccount;
import com.myGengo.alfresco.account.MyGengoAccountService;
import com.myGengo.alfresco.account.MyGengoServiceException;
import com.myGengo.alfresco.model.MyGengoModel;
import com.myGengo.alfresco.polling.MyGengoCommentBatchProcessWorker;
import com.myGengo.alfresco.polling.MyGengoJobBatchEntry;
import com.myGengo.alfresco.polling.MyGengoJobsBatchProcessWorker;
import com.myGengo.alfresco.utils.MyGengoUtils;
import com.mygengo.client.MyGengoClient;
import com.mygengo.client.enums.Rating;
import com.mygengo.client.enums.RejectReason;
import com.mygengo.client.enums.Tier;
import com.mygengo.client.exceptions.ErrorResponseException;
import com.mygengo.client.payloads.Payloads;
import com.mygengo.client.payloads.TranslationJob;

public class MyGengoTranslationServiceImpl implements MyGengoTranslationService {
    private static final String COMMENTS_TOPIC_NAME = "Comments";

    /**
      * Logger instance.
      */
    private static final Log LOGGER = LogFactory.getLog(MyGengoTranslationServiceImpl.class);

    private boolean useSandbox = false;
    private NodeService nodeService = null;
    private ContentService contentService = null;
    private MyGengoAccountService accountService;
    private TransactionService transactionService;
    private JobLockService jobLockService;

    @Override
    public Collection<MyGengoLanguagePair> refreshLanguagePairs(MyGengoAccount account, NodeRef nodeRef)
            throws MyGengoServiceException {
        MyGengoClient client = new MyGengoClient(account.getPublicKey(), account.getPrivateKey(), useSandbox);
        Collection<MyGengoLanguagePair> languagePairs = new ArrayList<MyGengoLanguagePair>();
        Map<String, MyGengoLanguage> languages = getLanguages(account);
        try {
            for (String languageCode : languages.keySet()) {
                JSONObject response = client.getServiceLanguagePairs(languageCode); //http://mygengo.com/api/developer-docs/methods/translate-service-language-pairs-get/
                if (response.has("response")) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("refreshLanguagePairs: " + response.toString());
                    }
                    JSONArray languagePairsJson = response.getJSONArray("response");
                    for (int i = 0; i < languagePairsJson.length(); i++) {
                        JSONObject languagePairJson = languagePairsJson.getJSONObject(i);
                        MyGengoLanguagePair languagePair = new MyGengoLanguagePair();
                        languagePair.setSource(languages.get(languageCode));
                        languagePair.setTarget(languages.get(languagePairJson.getString("lc_tgt")));
                        languagePair.setTier(Tier.valueOf(languagePairJson.getString("tier").toUpperCase()));
                        languagePair.setUnitPrice(languagePairJson.getString("unit_price"));
                        if (languagePair.getTier() == Tier.PRO || languagePair.getTier() == Tier.STANDARD
                                || languagePair.getTier() == Tier.ULTRA) {
                            languagePairs.add(languagePair);
                        }
                    }
                }
            }

            saveLanguagePairs(nodeRef, languagePairs);

        } catch (Throwable e) {
            LOGGER.error("loading LanguagePairs failed", e);
            throw new MyGengoServiceException("loading Language Pairs failed", e);
        }

        return languagePairs;
    }

    @Override
    public Map<String, MyGengoLanguage> getLanguages(NodeRef nodeRef) throws MyGengoServiceException {
        Map<String, MyGengoLanguage> languages = new HashMap<String, MyGengoLanguage>();
        for (MyGengoLanguagePair myGengoLanguagePair : this.getLanguagePairs(nodeRef)) {
            MyGengoLanguage source = myGengoLanguagePair.getSource();
            if (!languages.containsKey(source.getLanguageCode())) {
                languages.put(source.getLanguageCode(), source);
            }
            MyGengoLanguage target = myGengoLanguagePair.getTarget();
            if (!languages.containsKey(target.getLanguageCode())) {
                languages.put(target.getLanguageCode(), target);
            }
        }
        return languages;
    }

    @Override
    public Collection<MyGengoLanguagePair> getLanguagePairs(NodeRef nodeRef) throws MyGengoServiceException {
        if (!nodeService.hasAspect(nodeRef, MyGengoModel.ASPECT_LANGUAGES)) {
            return Collections.emptyList();
        }
        try {
            JSONArray languages = new JSONArray(
                    (String) nodeService.getProperty(nodeRef, MyGengoModel.PROP_LANGUAGES));
            int length = languages.length();
            Collection<MyGengoLanguagePair> languagePairs = new ArrayList<MyGengoLanguagePair>(length);
            for (int i = 0; i < length; i++) {
                MyGengoLanguagePair languagePair = new MyGengoLanguagePair(languages.getJSONObject(i));
                languagePairs.add(languagePair);
            }
            return languagePairs;
        } catch (Throwable e) {
            LOGGER.error("getting LanguagePairs failed", e);
            throw new MyGengoServiceException("getting LanguagePairs failed", e);
        }
    }

    @Override
    public MyGengoQuote getQuote(MyGengoAccount account, TranslationJob job) throws MyGengoServiceException {
        try {
            MyGengoClient myGengo = new MyGengoClient(account.getPublicKey(), account.getPrivateKey(),
                    this.useSandbox);
            Payloads jobList = new Payloads();
            jobList.add(job);
            JSONObject response = myGengo.determineTranslationCost(jobList);
            if (response.has("response")) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("getQuote: " + response.toString());
                }
                JSONArray jobs = response.getJSONObject("response").getJSONArray("jobs");
                JSONObject job1 = jobs.getJSONObject(0);
                return new MyGengoQuote(job1.getLong("unit_count"), job1.getDouble("credits"), job1.getLong("eta"));

            } else {
                throw new MyGengoServiceException("getting Quote failed");
            }
        } catch (Throwable e) {
            LOGGER.error("getting Quote failed", e);
            throw new MyGengoServiceException("getting Quote failed", e);
        }
    }

    public NodeRef orderTranslation(NodeRef parentRef, MyGengoAccount account, TranslationJob job)
            throws MyGengoServiceException {
        try {
            MyGengoClient myGengo = new MyGengoClient(account.getPublicKey(), account.getPrivateKey(),
                    this.useSandbox);
            JSONObject response = myGengo.postTranslationJob(job);
            if (response.has("response")) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("create job: " + response.toString());
                }
                Collection<MyGengoLanguagePair> languagePairs = getLanguagePairs(parentRef);
                Map<QName, Serializable> jobProperties = getJobProperties(
                        response.getJSONObject("response").getJSONObject("job"), languagePairs);
                String nodeName = GUID.generate();
                jobProperties.put(ContentModel.PROP_NAME, nodeName);
                NodeRef jobNodeRef = this.nodeService.createNode(parentRef, ContentModel.ASSOC_CONTAINS,
                        QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,
                                QName.createValidLocalName(nodeName)),
                        MyGengoModel.TYPE_TRANSLATIONJOB, jobProperties).getChildRef();

                //add comment if provided
                if (StringUtils.isNotBlank(job.getComment())) {
                    NodeRef commentsFolder = getOrCreateCommentsFolder(jobNodeRef);
                    String name = GUID.generate();
                    ChildAssociationRef createdNode = nodeService.createNode(commentsFolder,
                            ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,
                                    QName.createValidLocalName(name)),
                            ForumModel.TYPE_POST);
                    ContentWriter writer = contentService.getWriter(createdNode.getChildRef(),
                            ContentModel.PROP_CONTENT, true);
                    writer.setMimetype("text/plain");
                    writer.putContent(job.getComment());
                }

                return jobNodeRef;
            } else {
                throw new MyGengoServiceException("orderTranslation failed");
            }
        } catch (ErrorResponseException e) {
            LOGGER.error("orderTranslation failed", e);
            if (e.getErrorCode() == ErrorResponseException.CREDITS_INSUFFICIENT) {
                throw new MyGengoServiceException("insufficient credits", e);
            } else {
                throw new MyGengoServiceException("orderTranslation failed", e);
            }
        } catch (Throwable e) {
            LOGGER.error("orderTranslation failed", e);
            throw new MyGengoServiceException("orderTranslation failed", e);
        }
    }

    @Override
    public void cancelJob(NodeRef nodeRef, MyGengoAccount accountInfo) throws MyGengoServiceException {
        MyGengoClient myGengo = new MyGengoClient(accountInfo.getPublicKey(), accountInfo.getPrivateKey(),
                this.useSandbox);
        try {
            Integer jobId = Integer.valueOf((String) nodeService.getProperty(nodeRef, MyGengoModel.PROP_JOBID));
            JSONObject response = myGengo.deleteTranslationJob(jobId);
            if (response.has("response")) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("delete job: " + response.toString());
                }
                this.nodeService.deleteNode(nodeRef);
            }
        } catch (Throwable e) {
            LOGGER.error("cancelJob failed", e);
            throw new MyGengoServiceException("cancelJob failed", e);
        }

    }

    @Override
    public void refreshTranslation(NodeRef nodeRef, MyGengoAccount accountInfo) throws MyGengoServiceException {
        MyGengoClient myGengo = new MyGengoClient(accountInfo.getPublicKey(), accountInfo.getPrivateKey(),
                this.useSandbox);
        try {
            Integer jobId = Integer.valueOf((String) nodeService.getProperty(nodeRef, MyGengoModel.PROP_JOBID));
            JSONObject response = myGengo.getTranslationJob(jobId);
            if (response.has("response")) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("get job: " + response.toString());
                }
                Map<QName, Serializable> jobProperties = getJobProperties(
                        response.getJSONObject("response").getJSONObject("job"),
                        Collections.<MyGengoLanguagePair>emptyList());
                nodeService.addProperties(nodeRef, jobProperties);
                if (StringUtils.isNotBlank((String) jobProperties.get(MyGengoModel.PROP_PREVIEWURL))) {
                    BufferedImage translationJobPreviewImage = myGengo.getTranslationJobPreviewImage(jobId);
                    ContentWriter writer = contentService.getWriter(nodeRef, MyGengoModel.PROP_TRANSLATIONPREVIEW,
                            true);
                    writer.setMimetype("image/png");
                    writer.setEncoding("UTF-8");
                    OutputStream contentOutputStream = writer.getContentOutputStream();
                    ImageIO.write(translationJobPreviewImage, "png", contentOutputStream);
                    contentOutputStream.flush();
                    contentOutputStream.close();
                }

            }
        } catch (Throwable e) {
            LOGGER.error("refreshJob failed", e);
            throw new MyGengoServiceException("refreshJob failed", e);
        }
    }

    @Override
    public void refreshTranslationComments(NodeRef jobRef, MyGengoAccount accountInfo)
            throws MyGengoServiceException {
        MyGengoClient myGengo = new MyGengoClient(accountInfo.getPublicKey(), accountInfo.getPrivateKey(),
                this.useSandbox);
        try {
            Integer jobId = Integer.valueOf((String) nodeService.getProperty(jobRef, MyGengoModel.PROP_JOBID));
            List<ChildAssociationRef> comments = getComments(jobRef);
            List<CommentData> repoComments = new ArrayList<CommentData>(comments.size());
            for (ChildAssociationRef childAssociationRef : comments) {
                NodeRef commentRef = childAssociationRef.getChildRef();
                String contentString = contentService.getReader(commentRef, ContentModel.PROP_CONTENT)
                        .getContentString();
                //remove html tags because myGengo only uses text/plain comments
                contentString = HtmlToText.htmlToPlainText(StringUtils.trim(contentString));
                long timestamp = 0;
                if (nodeService.hasAspect(commentRef, MyGengoModel.ASPECT_COMMENT)) {
                    timestamp = (Long) nodeService.getProperty(commentRef, MyGengoModel.PROP_COMMENTMODIFIEDTIME);
                }
                repoComments.add(new CommentData(contentString, timestamp, commentRef));
            }

            JSONObject response = myGengo.getTranslationJobComments(jobId);
            if (response.has("response")) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("comment job: " + response.toString());
                }
                JSONArray jsonThread = response.getJSONObject("response").getJSONArray("thread");
                List<CommentData> addComments = new ArrayList<CommentData>();
                for (int i = 0; i < jsonThread.length(); i++) {
                    JSONObject jsonComment = jsonThread.getJSONObject(i);
                    String commentText = jsonComment.getString("body");
                    long timestamp = jsonComment.getLong("ctime");
                    CommentData jsonCommentData = new CommentData(
                            HtmlToText.htmlToPlainText(StringUtils.trim(commentText)), timestamp);
                    boolean add = true;
                    for (CommentData commentData : repoComments) {
                        if (!commentData.equals(jsonCommentData)) {
                            //comment not in sync
                            if (commentData.comment.equalsIgnoreCase(commentText)) {
                                //comment text known, timestamp needs to be updated
                                Map<QName, Serializable> aspectProperties = new HashMap<QName, Serializable>(1,
                                        1.0f);
                                aspectProperties.put(MyGengoModel.PROP_COMMENTMODIFIEDTIME, timestamp);
                                nodeService.addAspect(commentData.commentRef, MyGengoModel.ASPECT_COMMENT,
                                        aspectProperties);
                                add = false;
                            }
                        } else {
                            add = false;
                        }
                    }
                    if (add) {
                        addComments.add(jsonCommentData);
                    }
                }

                for (CommentData addComment : addComments) {
                    // text & timestamp unknown
                    NodeRef commentsFolder = getOrCreateCommentsFolder(jobRef);
                    String name = GUID.generate();
                    ChildAssociationRef createdNode = nodeService.createNode(commentsFolder,
                            ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,
                                    QName.createValidLocalName(name)),
                            ForumModel.TYPE_POST);
                    ContentWriter writer = contentService.getWriter(createdNode.getChildRef(),
                            ContentModel.PROP_CONTENT, true);
                    writer.setMimetype("text/plain");
                    writer.putContent(addComment.comment);
                    Map<QName, Serializable> aspectProperties = new HashMap<QName, Serializable>(1, 1.0f);
                    aspectProperties.put(MyGengoModel.PROP_COMMENTMODIFIEDTIME, addComment.timestamp);
                    nodeService.addAspect(createdNode.getChildRef(), MyGengoModel.ASPECT_COMMENT, aspectProperties);
                }
            }
        } catch (Exception e) {
            LOGGER.error("refreshJobComments failed", e);
            throw new MyGengoServiceException("refreshJobComments failed", e);
        }
    }

    @Override
    public void addJobComment(NodeRef jobRef, String comment, MyGengoAccount accountInfo)
            throws MyGengoServiceException {
        MyGengoClient myGengo = new MyGengoClient(accountInfo.getPublicKey(), accountInfo.getPrivateKey(),
                this.useSandbox);
        try {
            String jobId = (String) nodeService.getProperty(jobRef, MyGengoModel.PROP_JOBID);
            String commentAsText = HtmlToText.htmlToPlainText(StringUtils.trim(comment));
            JSONObject response = myGengo.postTranslationJobComment(Integer.valueOf(jobId), commentAsText);
            if (response.has("response")) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("comment job: " + response.toString());
                }
            } else {
                throw new MyGengoServiceException("adding comment failed");
            }
        } catch (Exception e) {
            LOGGER.error("adding comment failed", e);
            throw new MyGengoServiceException("refreshJobComments failed", e);
        }
    }

    @Override
    public void approveTranslation(NodeRef nodeRef, String feedbackTranslator, String feedbackMyGengo, int rating,
            boolean feedbackIsPublic, MyGengoAccount accountInfo) throws MyGengoServiceException {
        MyGengoClient myGengo = new MyGengoClient(accountInfo.getPublicKey(), accountInfo.getPrivateKey(),
                this.useSandbox);
        try {
            String jobId = (String) nodeService.getProperty(nodeRef, MyGengoModel.PROP_JOBID);
            JSONObject response = myGengo.approveTranslationJob(Integer.valueOf(jobId), Rating.getRating(rating),
                    feedbackTranslator, feedbackMyGengo, feedbackIsPublic);
            if (response.has("response")) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("approve job: " + response.toString());
                }
                this.refreshTranslation(nodeRef, accountInfo);
            } else {
                throw new MyGengoServiceException("approving job failed");
            }
        } catch (Exception e) {
            LOGGER.error("approving job failed", e);
            throw new MyGengoServiceException("approving job failed", e);
        }

    }

    @Override
    public void reviseTranslation(NodeRef nodeRef, String reviseComment, MyGengoAccount accountInfo)
            throws MyGengoServiceException {
        MyGengoClient myGengo = new MyGengoClient(accountInfo.getPublicKey(), accountInfo.getPrivateKey(),
                this.useSandbox);
        try {
            String jobId = (String) nodeService.getProperty(nodeRef, MyGengoModel.PROP_JOBID);
            JSONObject response = myGengo.reviseTranslationJob(Integer.valueOf(jobId), reviseComment);
            if (response.has("response")) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("revise job: " + response.toString());
                }
                this.refreshTranslation(nodeRef, accountInfo);
            } else {
                throw new MyGengoServiceException("revise job failed");
            }
        } catch (Exception e) {
            LOGGER.error("revise failed", e);
            throw new MyGengoServiceException("revise failed", e);
        }
    }

    @Override
    public void rejectTranslation(NodeRef nodeRef, String rejectComment, String rejectReason, String captcha,
            boolean requeue, MyGengoAccount accountInfo) throws MyGengoServiceException {
        MyGengoClient myGengo = new MyGengoClient(accountInfo.getPublicKey(), accountInfo.getPrivateKey(),
                this.useSandbox);
        try {
            String jobId = (String) nodeService.getProperty(nodeRef, MyGengoModel.PROP_JOBID);
            JSONObject response = myGengo.rejectTranslationJob(Integer.valueOf(jobId),
                    RejectReason.valueOf(rejectReason.toUpperCase()), rejectComment, captcha, requeue);
            if (response.has("response")) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("reject job: " + response.toString());
                }
                this.refreshTranslation(nodeRef, accountInfo);
            } else {
                throw new MyGengoServiceException("reject job failed");
            }
        } catch (Exception e) {
            LOGGER.error("reject failed", e);
            //refresh translation to fetch new captcha URL
            this.refreshTranslation(nodeRef, accountInfo);
            throw new MyGengoServiceException("reject failed", e);
        }

    }

    @Override
    public void refreshTranslations(NodeRef containerRef, MyGengoAccount accountInfo, boolean includeApproved)
            throws MyGengoServiceException {
        String lock = null;
        try {
            LOGGER.info("getting lock");
            lock = jobLockService.getLock(MyGengoTranslationService.LOCK_QNAME, MyGengoTranslationService.LOCK_TTL,
                    0, 1);
            jobLockService.refreshLock(lock, MyGengoTranslationService.LOCK_QNAME,
                    MyGengoTranslationService.LOCK_TTL, new JobLockService.JobLockRefreshCallback() {

                        public void lockReleased() {
                            LOGGER.info("lock released");
                        }

                        public boolean isActive() {
                            return true;
                        }
                    });
            Set<NodeRef> jobRefs = MyGengoUtils.getMyGengoJobs(containerRef, this.nodeService, includeApproved);
            final Map<MyGengoAccount, Collection<NodeRef>> myGengoJobs = new HashMap<MyGengoAccount, Collection<NodeRef>>(
                    1, 1.0f);
            myGengoJobs.put(accountInfo, jobRefs);

            final ConcurrentHashMap<NodeRef, MyGengoAccount> accountMap = new ConcurrentHashMap<NodeRef, MyGengoAccount>(
                    20);

            BatchProcessWorker<NodeRef> worker = new MyGengoCommentBatchProcessWorker(accountMap, nodeService,
                    accountService, this, AuthenticationUtil.getFullyAuthenticatedUser());
            BatchProcessor<NodeRef> batchProcessor = new BatchProcessor<NodeRef>(
                    "MyGengoRefreshTranslationComments", transactionService.getRetryingTransactionHelper(),
                    MyGengoUtils.getTranslationCommentsProvider(jobRefs), 10, 5, null, LOGGER, 5);
            batchProcessor.process(worker, false);

            BatchProcessWorker<MyGengoJobBatchEntry> jobWorker = new MyGengoJobsBatchProcessWorker(this,
                    AuthenticationUtil.getFullyAuthenticatedUser());
            BatchProcessor<MyGengoJobBatchEntry> jobPatchProcessor = new BatchProcessor<MyGengoJobBatchEntry>(
                    "MyGengoRefreshTranslations", transactionService.getRetryingTransactionHelper(),
                    MyGengoUtils.getTranslationsProvider(myGengoJobs), 5, 1, null, LOGGER, 1);
            jobPatchProcessor.process(jobWorker, false);
        } finally {
            try {
                jobLockService.releaseLock(lock, MyGengoTranslationService.LOCK_QNAME);
            } catch (Exception e) {
                LOGGER.warn("releasing lock failed", e);
            }
        }
    }

    @Override
    public void refreshTranslations(MyGengoAccount accountInfo, Collection<NodeRef> jobRefs)
            throws MyGengoServiceException {
        MyGengoClient myGengo = new MyGengoClient(accountInfo.getPublicKey(), accountInfo.getPrivateKey(),
                this.useSandbox);
        try {
            Map<Integer, NodeRef> jobs = new HashMap<Integer, NodeRef>(jobRefs.size());
            for (NodeRef jobRef : jobRefs) {
                jobs.put(Integer.valueOf((String) nodeService.getProperty(jobRef, MyGengoModel.PROP_JOBID)),
                        jobRef);
            }
            LOGGER.debug("trying to refresh jobs via REST..." + jobRefs.size());
            JSONObject response = myGengo.getTranslationJobs(Lists.newArrayList(jobs.keySet()));
            if (response.has("response")) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("refresh jobs: " + response.toString());
                }
                JSONArray jsonJobs = response.getJSONObject("response").getJSONArray("jobs");
                for (int i = 0; i < jsonJobs.length(); i++) {
                    JSONObject jsonJob = jsonJobs.getJSONObject(i);
                    Map<QName, Serializable> jobProperties = getJobProperties(jsonJob,
                            Collections.<MyGengoLanguagePair>emptyList());
                    Integer jobId = Integer.valueOf((String) jobProperties.get(MyGengoModel.PROP_JOBID));
                    NodeRef jobRef = jobs.get(jobId);
                    nodeService.addProperties(jobRef, jobProperties);
                    if (StringUtils.isNotBlank((String) jobProperties.get(MyGengoModel.PROP_PREVIEWURL))) {
                        BufferedImage translationJobPreviewImage = myGengo.getTranslationJobPreviewImage(jobId);
                        ContentWriter writer = contentService.getWriter(jobRef,
                                MyGengoModel.PROP_TRANSLATIONPREVIEW, true);
                        writer.setMimetype("image/png");
                        writer.setEncoding("UTF-8");
                        OutputStream contentOutputStream = writer.getContentOutputStream();
                        ImageIO.write(translationJobPreviewImage, "png", contentOutputStream);
                        contentOutputStream.flush();
                        contentOutputStream.close();
                    }
                }
            } else {
                throw new MyGengoServiceException("refresh jobs failed");
            }
        } catch (Exception e) {
            LOGGER.error("refresh jobs", e);
            throw new MyGengoServiceException("refresh jobs", e);
        }

    }

    private class CommentData {
        private final String comment;
        private final long timestamp;
        private NodeRef commentRef;

        public CommentData(String comment, long timestamp, NodeRef commentRef) {
            this.comment = comment;
            this.timestamp = timestamp;
            this.commentRef = commentRef;
        }

        public CommentData(String comment, long timestamp) {
            this.comment = comment;
            this.timestamp = timestamp;
        }

        /* (non-Javadoc)
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + getOuterType().hashCode();
            result = prime * result + ((comment == null) ? 0 : comment.hashCode());
            result = prime * result + (int) (timestamp ^ (timestamp >>> 32));
            return result;
        }

        /* (non-Javadoc)
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (!(obj instanceof CommentData)) {
                return false;
            }
            CommentData other = (CommentData) obj;
            if (!getOuterType().equals(other.getOuterType())) {
                return false;
            }
            if (comment == null) {
                if (other.comment != null) {
                    return false;
                }
            } else if (!comment.equals(other.comment)) {
                return false;
            }
            if (timestamp != other.timestamp) {
                return false;
            }
            return true;
        }

        private MyGengoTranslationServiceImpl getOuterType() {
            return MyGengoTranslationServiceImpl.this;
        }

    }

    private Map<QName, Serializable> getJobProperties(JSONObject jsonJob,
            Collection<MyGengoLanguagePair> languagePairs) throws JSONException {
        Map<QName, Serializable> jobProperties = new HashMap<QName, Serializable>();
        String jobId = jsonJob.getString("job_id");
        jobProperties.put(MyGengoModel.PROP_JOBID, jobId);
        jobProperties.put(MyGengoModel.PROP_TEXT, jsonJob.getString("body_src"));
        final String sourceLang = jsonJob.getString("lc_src");
        final String targetLang = jsonJob.getString("lc_tgt");
        jobProperties.put(MyGengoModel.PROP_SOURCELANGUAGE, sourceLang);
        jobProperties.put(MyGengoModel.PROP_TARGETLANGUAGE, targetLang);
        String tier = jsonJob.getString("tier");
        //workaround: we sometimes get ultra_pro as tier where it should be ultra
        if ("ultra_pro".equalsIgnoreCase(tier)) {
            tier = "ultra";
        }
        jobProperties.put(MyGengoModel.PROP_TIER, tier);
        if (jsonJob.has("slug")) {
            jobProperties.put(MyGengoModel.PROP_TITLE, jsonJob.getString("slug"));
        }
        jobProperties.put(MyGengoModel.PROP_STATUS, jsonJob.getString("status"));
        jobProperties.put(MyGengoModel.PROP_UNITCOUNT, jsonJob.getLong("unit_count"));
        jobProperties.put(MyGengoModel.PROP_JOBCREDITS, jsonJob.getString("credits"));
        jobProperties.put(MyGengoModel.PROP_ETA, jsonJob.getLong("eta"));
        jobProperties.put(MyGengoModel.PROP_MODIFIED, new Date());
        jobProperties.put(MyGengoModel.PROP_AUTOAPPROVE,
                MyGengoClient.MYGENGO_TRUE.equalsIgnoreCase(jsonJob.getString("auto_approve")));
        if (jsonJob.has("captcha_url")) {
            jobProperties.put(MyGengoModel.PROP_CAPTCHAURL, jsonJob.getString("captcha_url"));
        }
        if (jsonJob.has("preview_url")) {
            jobProperties.put(MyGengoModel.PROP_PREVIEWURL, jsonJob.getString("preview_url"));
        }
        if (jsonJob.has("body_tgt")) {
            jobProperties.put(MyGengoModel.PROP_TRANSLATION, jsonJob.getString("body_tgt"));
        }

        for (MyGengoLanguagePair myGengoLanguagePair : languagePairs) {
            if (myGengoLanguagePair.getSource().getLanguageCode().equalsIgnoreCase(sourceLang)
                    && myGengoLanguagePair.getTarget().getLanguageCode().equalsIgnoreCase(targetLang)) {
                jobProperties.put(MyGengoModel.PROP_UNITTYPE, myGengoLanguagePair.getSource().getUnitType());
                break;
            }
        }
        return jobProperties;
    }

    private void saveLanguagePairs(NodeRef nodeRef, Collection<MyGengoLanguagePair> languagePairs)
            throws MyGengoServiceException {
        JSONStringer stringer = new JSONStringer();
        try {
            stringer.array();
            for (MyGengoLanguagePair languagePair : languagePairs) {
                stringer.value(languagePair);
            }
            stringer.endArray();
            String jsonString = stringer.toString();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("writing JSON: " + jsonString);
            }
            if (nodeService.hasAspect(nodeRef, MyGengoModel.ASPECT_LANGUAGES)) {
                nodeService.setProperty(nodeRef, MyGengoModel.PROP_LANGUAGES, jsonString);
            } else {
                Map<QName, Serializable> aspectProperties = new HashMap<QName, Serializable>();
                aspectProperties.put(MyGengoModel.PROP_LANGUAGES, jsonString);
                nodeService.addAspect(nodeRef, MyGengoModel.ASPECT_LANGUAGES, aspectProperties);
            }

        } catch (Throwable e) {
            LOGGER.error("saving LanguagePairs failed", e);
            throw new MyGengoServiceException("saving LanguagePairs failed", e);
        }

    }

    private Map<String, MyGengoLanguage> getLanguages(MyGengoAccount account) throws MyGengoServiceException {
        MyGengoClient client = new MyGengoClient(account.getPublicKey(), account.getPrivateKey(), useSandbox);
        Map<String, MyGengoLanguage> languages = new HashMap<String, MyGengoLanguage>();
        try {
            JSONObject response = client.getServiceLanguages(); //http://mygengo.com/api/developer-docs/methods/translate-service-languages-get/
            if (response.has("response")) {
                JSONArray languagesJson = response.getJSONArray("response");
                for (int i = 0; i < languagesJson.length(); i++) {
                    JSONObject languageJson = languagesJson.getJSONObject(i);
                    MyGengoLanguage language = new MyGengoLanguage();
                    language.setLanguage(languageJson.getString("language"));
                    language.setLanguageCode(languageJson.getString("lc"));
                    language.setLocalizedName(languageJson.getString("localized_name"));
                    language.setUnitType(languageJson.getString("unit_type"));
                    languages.put(language.getLanguageCode(), language);
                }
            }

        } catch (Throwable e) {
            LOGGER.error("loading Languages failed", e);
            throw new MyGengoServiceException("loading Languages failed", e);
        }
        return languages;
    }

    private List<ChildAssociationRef> getComments(NodeRef jobRef) {
        List<ChildAssociationRef> assocs = nodeService.getChildAssocs(jobRef,
                QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "discussion"),
                RegexQNamePattern.MATCH_ALL);
        if (assocs.size() != 0) {
            NodeRef forumFolder = assocs.get(0).getChildRef();
            List<ChildAssociationRef> topicsAssocs = nodeService.getChildAssocs(forumFolder,
                    ContentModel.ASSOC_CONTAINS,
                    QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, COMMENTS_TOPIC_NAME));

            if (topicsAssocs.size() != 0) {
                NodeRef topic = topicsAssocs.get(0).getChildRef();
                Set<QName> postType = new HashSet<QName>(1, 1.0f);
                postType.add(ForumModel.TYPE_POST);
                return nodeService.getChildAssocs(topic, postType);
            }
        }
        return Collections.<ChildAssociationRef>emptyList();
    }

    public void setUseSandbox(boolean useSandbox) {
        this.useSandbox = useSandbox;
    }

    public void setNodeService(NodeService nodeService) {
        this.nodeService = nodeService;
    }

    public void setContentService(ContentService contentService) {
        this.contentService = contentService;
    }

    public void setAccountService(MyGengoAccountService accountService) {
        this.accountService = accountService;
    }

    public void setTransactionService(TransactionService transactionService) {
        this.transactionService = transactionService;
    }

    public void setJobLockService(JobLockService jobLockService) {
        this.jobLockService = jobLockService;
    }

    protected NodeRef getOrCreateCommentsFolder(NodeRef nodeRef) {
        NodeRef commentsFolder = getCommentsFolder(nodeRef);
        if (commentsFolder == null) {
            commentsFolder = createCommentsFolder(nodeRef);
        }
        return commentsFolder;
    }

    private NodeRef getCommentsFolder(NodeRef nodeRef) {
        NodeRef commentsFolder = null;

        Set<QName> types = new HashSet<QName>(1, 1.0f);
        types.add(ForumModel.TYPE_FORUM);
        List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(nodeRef, types);
        if (childAssocs.size() > 0) {
            NodeRef discussionFolder = childAssocs.get(0).getChildRef();
            commentsFolder = nodeService.getChildByName(discussionFolder, ContentModel.ASSOC_CONTAINS,
                    COMMENTS_TOPIC_NAME);
        }
        return commentsFolder;
    }

    private NodeRef createCommentsFolder(NodeRef nodeRef) {
        NodeRef commentsFolder = null;
        nodeService.addAspect(nodeRef, QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "discussable"),
                null);
        List<ChildAssociationRef> assocs = nodeService.getChildAssocs(nodeRef,
                QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "discussion"),
                RegexQNamePattern.MATCH_ALL);
        if (assocs.size() != 0) {
            NodeRef forumFolder = assocs.get(0).getChildRef();

            Map<QName, Serializable> props = new HashMap<QName, Serializable>(1);
            props.put(ContentModel.PROP_NAME, COMMENTS_TOPIC_NAME);
            commentsFolder = nodeService.createNode(forumFolder, ContentModel.ASSOC_CONTAINS,
                    QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, COMMENTS_TOPIC_NAME),
                    QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "topic"), props).getChildRef();
        }

        return commentsFolder;
    }
}