private void prepareTransUnitUpdatedEvent(
      int previousVersionNum, ContentState previousState, HTextFlowTarget target) {
    LocaleId localeId = target.getLocaleId();
    HTextFlow textFlow = target.getTextFlow();
    HDocument document = textFlow.getDocument();
    HProjectIteration projectIteration = document.getProjectIteration();
    String iterationSlug = projectIteration.getSlug();
    String projectSlug = projectIteration.getProject().getSlug();
    ProjectType projectType = projectIteration.getProjectType();

    WorkspaceId workspaceId =
        new WorkspaceId(new ProjectIterationId(projectSlug, iterationSlug, projectType), localeId);
    Optional<TranslationWorkspace> workspaceOptional =
        translationWorkspaceManager.tryGetWorkspace(workspaceId);
    if (!workspaceOptional.isPresent()) {
      return;
    }

    TransUnitTransformer transUnitTransformer =
        serviceLocator.getInstance(TransUnitTransformer.class);
    TransUnit transUnit = transUnitTransformer.transform(textFlow, target, target.getLocale());

    DocumentId documentId = new DocumentId(document.getId(), document.getDocId());
    int wordCount = textFlow.getWordCount().intValue();

    TransUnitUpdateInfo updateInfo =
        createTransUnitUpdateInfo(
            transUnit, documentId, wordCount, previousVersionNum, previousState);

    CacheValue context =
        updateContext.getIfPresent(new CacheKey(transUnit.getId(), transUnit.getLocaleId()));
    TransUnitUpdated updated;
    if (context != null) {
      updated = new TransUnitUpdated(updateInfo, context.editorClientId, context.updateType);
      log.debug("about to publish trans unit updated event {}", updated);
    } else if (ServletContexts.instance().getRequest() != null) {

      String sessionId = ServletContexts.instance().getRequest().getSession().getId();
      EditorClientId editorClientId = new EditorClientId(sessionId, -1);
      updated =
          new TransUnitUpdated(
              updateInfo, editorClientId, TransUnitUpdated.UpdateType.NonEditorSave);
    } else {
      updated =
          new TransUnitUpdated(
              updateInfo,
              new EditorClientId("unknown", -1),
              TransUnitUpdated.UpdateType.NonEditorSave);
    }
    if (Events.exists()) {
      Events.instance()
          .raiseTransactionSuccessEvent(
              TextFlowTargetUpdatedEvent.EVENT_NAME,
              new TextFlowTargetUpdatedEvent(workspaceOptional.get(), target.getId(), updated));
    }
  }
  private void signalCopiedTranslation(
      HTextFlowTarget target, ContentState previousState, Long wordCount) {
    /*
     * Using a direct method call instead of an event because it's easier to
     * read. Since these events are being called synchronously (as opposed
     * to an 'after Transaction' events), there is no big performance gain
     * and makes the code easier to read and navigate.
     */
    // TODO how was this not causing duplicate events?  Is this bypassing TranslationServiceImpl?
    // FIXME other observers may not be notified
    HDocument document = target.getTextFlow().getDocument();

    DocumentLocaleKey key = new DocumentLocaleKey(document.getId(), target.getLocaleId());

    Map<ContentState, Long> contentStates = Maps.newHashMap();
    DocStatsEvent.updateContentStateDeltas(
        contentStates, target.getState(), previousState, wordCount);

    DocStatsEvent docEvent =
        new DocStatsEvent(
            key, document.getProjectIteration().getId(), contentStates, target.getId());

    versionStateCacheImpl.docStatsUpdated(docEvent);
  }
  private void saveCopyTransMatch(
      Long actorId,
      final HTextFlowTarget matchingTarget,
      final HTextFlow originalTf,
      final HCopyTransOptions options,
      final boolean requireTranslationReview) {
    final HProjectIteration matchingTargetProjectIteration =
        matchingTarget.getTextFlow().getDocument().getProjectIteration();
    // lazy evaluation of some conditions
    Supplier<Boolean> contextMatches =
        new Supplier<Boolean>() {
          @Override
          public Boolean get() {
            return originalTf.getResId().equals(matchingTarget.getTextFlow().getResId());
          }
        };
    Supplier<Boolean> projectMatches =
        new Supplier<Boolean>() {
          public Boolean get() {
            return originalTf
                .getDocument()
                .getProjectIteration()
                .getProject()
                .getId()
                .equals(matchingTargetProjectIteration.getProject().getId());
          }
        };
    Supplier<Boolean> docIdMatches =
        new Supplier<Boolean>() {
          @Override
          public Boolean get() {
            return originalTf
                .getDocument()
                .getDocId()
                .equals(matchingTarget.getTextFlow().getDocument().getDocId());
          }
        };
    final ContentState copyState =
        determineContentState(
            contextMatches,
            projectMatches,
            docIdMatches,
            options,
            requireTranslationReview,
            matchingTarget.getState());

    boolean hasValidationError =
        validationTranslations(
            copyState,
            matchingTargetProjectIteration,
            originalTf.getContents(),
            matchingTarget.getContents());

    if (hasValidationError) {
      return;
    }

    HTextFlowTarget hTarget =
        textFlowTargetDAO.getOrCreateTarget(originalTf, matchingTarget.getLocale());
    ContentState prevState = hTarget.getId() == null ? ContentState.New : hTarget.getState();
    if (shouldOverwrite(hTarget, copyState)) {
      // NB we don't touch creationDate
      hTarget.setTextFlowRevision(originalTf.getRevision());
      hTarget.setLastChanged(matchingTarget.getLastChanged());
      hTarget.setLastModifiedBy(matchingTarget.getLastModifiedBy());
      hTarget.setTranslator(matchingTarget.getTranslator());
      // TODO rhbz953734 - will need a new copyTran option for
      // review state
      if (copyState == ContentState.Approved) {
        hTarget.setReviewer(matchingTarget.getReviewer());
      }
      hTarget.setContents(matchingTarget.getContents());
      hTarget.setState(copyState);
      if (matchingTarget.getComment() == null) {
        hTarget.setComment(null);
      } else {
        HSimpleComment hComment = hTarget.getComment();
        if (hComment == null) {
          hComment = new HSimpleComment();
          hTarget.setComment(hComment);
        }
        hComment.setComment(matchingTarget.getComment().getComment());
      }
      hTarget.setRevisionComment(TranslationUtil.getCopyTransMessage(matchingTarget));
      hTarget.setSourceType(TranslationSourceType.COPY_TRANS);

      TranslationUtil.copyEntity(matchingTarget, hTarget);

      // TODO Maybe we should think about registering a Hibernate
      // integrator for these updates
      signalCopiedTranslation(hTarget, prevState, originalTf.getWordCount());
    }
  }