Esempio n. 1
0
  /**
   * Commit the passed in changed cube records identified by NCubeInfoDtos.
   *
   * @return array of NCubeInfoDtos that are to be committed.
   */
  public static List<NCubeInfoDto> commitBranch(
      ApplicationID appId, Object[] infoDtos, String username) {
    validateAppId(appId);
    appId.validateBranchIsNotHead();
    appId.validateStatusIsNotRelease();

    ApplicationID headAppId = appId.asHead();
    Map<String, NCubeInfoDto> headMap = new TreeMap<>();
    Map<String, Object> options = new HashMap<>();
    options.put(SEARCH_ACTIVE_RECORDS_ONLY, false);
    List<NCubeInfoDto> headInfo = search(headAppId, null, null, options);

    //  build map of head objects for reference.
    for (NCubeInfoDto info : headInfo) {
      headMap.put(info.name, info);
    }

    List<NCubeInfoDto> dtosToUpdate = new ArrayList<>(infoDtos.length);
    List<NCubeInfoDto> dtosMerged = new ArrayList<>();

    Map<String, Map> errors = new LinkedHashMap<>();

    for (Object dto : infoDtos) {
      NCubeInfoDto branchCubeInfo = (NCubeInfoDto) dto;

      if (!branchCubeInfo.isChanged()) {
        continue;
      }
      if (branchCubeInfo.sha1 == null) {
        branchCubeInfo.sha1 = "";
      }

      // All changes go through here.
      NCubeInfoDto headCubeInfo = headMap.get(branchCubeInfo.name);
      long infoRev = (long) Converter.convert(branchCubeInfo.revision, long.class);

      if (headCubeInfo == null) { // No matching head cube, CREATE case
        if (infoRev
            >= 0) { // Only create if the cube in the branch is active (revision number not
                    // negative)
          dtosToUpdate.add(branchCubeInfo);
        }
      } else if (StringUtilities.equalsIgnoreCase(
          branchCubeInfo.headSha1,
          headCubeInfo
              .sha1)) { // HEAD cube has not changed (at least in terms of SHA-1 it could have it's
                        // revision sign changed)
        if (StringUtilities.equalsIgnoreCase(
            branchCubeInfo.sha1,
            branchCubeInfo
                .headSha1)) { // Cubes are same, but active status could be opposite (delete or
                              // restore case)
          long headRev = (long) Converter.convert(headCubeInfo.revision, long.class);
          if ((infoRev < 0) != (headRev < 0)) {
            dtosToUpdate.add(branchCubeInfo);
          }
        } else { // Regular update case (branch updated cube that was not touched in HEAD)
          dtosToUpdate.add(branchCubeInfo);
        }
      } else if (StringUtilities.equalsIgnoreCase(
          branchCubeInfo.sha1,
          headCubeInfo
              .sha1)) { // Branch headSha1 does not match HEAD sha1, but it's SHA-1 matches the HEAD
                        // SHA-1.
        // This means that the branch cube and HEAD cube are identical, but the HEAD was
        // different when the branch was created.
        dtosToUpdate.add(branchCubeInfo);
      } else {
        String msg;
        if (branchCubeInfo.headSha1 == null) {
          msg = ". A cube with the same name was added to HEAD since your branch was created.";
        } else {
          msg = ". The cube changed since your last update branch.";
        }
        String message = "Conflict merging " + branchCubeInfo.name + msg;
        NCube mergedCube =
            checkForConflicts(appId, errors, message, branchCubeInfo, headCubeInfo, false);
        if (mergedCube != null) {
          NCubeInfoDto mergedDto =
              getPersister().commitMergedCubeToHead(appId, mergedCube, username);
          dtosMerged.add(mergedDto);
        }
      }
    }

    if (!errors.isEmpty()) {
      throw new BranchMergeException(
          errors.size()
              + " merge conflict(s) committing branch.  Update your branch and retry commit.",
          errors);
    }

    List<NCubeInfoDto> committedCubes = new ArrayList<>(dtosToUpdate.size());
    Object[] ids = new Object[dtosToUpdate.size()];
    int i = 0;
    for (NCubeInfoDto dto : dtosToUpdate) {
      ids[i++] = dto.id;
    }

    committedCubes.addAll(getPersister().commitCubes(appId, ids, username));
    committedCubes.addAll(dtosMerged);
    clearCache(appId);
    clearCache(headAppId);
    broadcast(appId);
    return committedCubes;
  }
Esempio n. 2
0
  /**
   * Update a branch from the HEAD. Changes from the HEAD are merged into the supplied branch. If
   * the merge cannot be done perfectly, an exception is thrown indicating the cubes that are in
   * conflict.
   */
  public static Map<String, Object> updateBranch(ApplicationID appId, String username) {
    validateAppId(appId);
    appId.validateBranchIsNotHead();
    appId.validateStatusIsNotRelease();

    ApplicationID headAppId = appId.asHead();

    Map<String, Object> options = new HashMap<>();
    options.put(SEARCH_ACTIVE_RECORDS_ONLY, false);
    List<NCubeInfoDto> records = search(appId, null, null, options);
    Map<String, NCubeInfoDto> branchRecordMap = new CaseInsensitiveMap<>();

    for (NCubeInfoDto info : records) {
      branchRecordMap.put(info.name, info);
    }

    List<NCubeInfoDto> updates = new ArrayList<>();
    List<NCubeInfoDto> dtosMerged = new ArrayList<>();
    Map<String, Map> conflicts = new CaseInsensitiveMap<>();
    List<NCubeInfoDto> headRecords = search(headAppId, null, null, options);

    for (NCubeInfoDto head : headRecords) {
      NCubeInfoDto info = branchRecordMap.get(head.name);

      if (info == null) { // HEAD has cube that branch does not have
        updates.add(head);
        continue;
      }

      long infoRev = (long) Converter.convert(info.revision, long.class);
      long headRev = (long) Converter.convert(head.revision, long.class);
      boolean activeStatusMatches = (infoRev < 0) == (headRev < 0);

      // Did branch change?
      if (!info.isChanged()) { // No change on branch
        if (!activeStatusMatches
            || !StringUtilities.equalsIgnoreCase(
                info.headSha1, head.sha1)) { // 1. The active/deleted statuses don't match, or
          // 2. HEAD has different SHA1 but branch cube did not change, safe to update branch (fast
          // forward)
          // In both cases, the cube was marked NOT changed in the branch, so safe to update.
          updates.add(head);
        }
      } else if (StringUtilities.equalsIgnoreCase(
          info.sha1,
          head.sha1)) { // If branch is 'changed' but has same SHA-1 as head, then see if branch
                        // needs Fast-Forward
        if (!StringUtilities.equalsIgnoreCase(info.headSha1, head.sha1)) { // Fast-Forward branch
          // Update HEAD SHA-1 on branch directly (no need to insert)
          getPersister()
              .updateBranchCubeHeadSha1((Long) Converter.convert(info.id, Long.class), head.sha1);
        }
      } else {
        if (!StringUtilities.equalsIgnoreCase(
            info.headSha1,
            head.sha1)) { // Cube is different than HEAD, AND it is not based on same HEAD cube, but
                          // it could be merge-able.
          String message = "Cube was changed in both branch and HEAD";
          NCube cube = checkForConflicts(appId, conflicts, message, info, head, true);

          if (cube != null) {
            NCubeInfoDto mergedDto =
                getPersister().commitMergedCubeToBranch(appId, cube, head.sha1, username);
            dtosMerged.add(mergedDto);
          }
        }
      }
    }

    List<NCubeInfoDto> finalUpdates = new ArrayList<>(updates.size());

    Object[] ids = new Object[updates.size()];
    int i = 0;
    for (NCubeInfoDto dto : updates) {
      ids[i++] = dto.id;
    }
    finalUpdates.addAll(getPersister().pullToBranch(appId, ids, username));

    clearCache(appId);

    Map<String, Object> ret = new LinkedHashMap<>();
    ret.put(BRANCH_UPDATES, finalUpdates);
    ret.put(BRANCH_MERGES, dtosMerged);
    ret.put(BRANCH_CONFLICTS, conflicts);
    return ret;
  }
Esempio n. 3
0
  /**
   * Get List<NCubeInfoDto> of n-cube record DTOs for the given ApplicationID (branch only). If
   * using For any cube record loaded, for which there is no entry in the app's cube cache, an entry
   * is added mapping the cube name to the cube record (NCubeInfoDto). This will be replaced by an
   * NCube if more than the name is required. one (1) character. This is universal whether using a
   * SQL perister or Mongo persister.
   */
  public static List<NCubeInfoDto> getBranchChangesFromDatabase(ApplicationID appId) {
    validateAppId(appId);
    if (appId.getBranch().equals(ApplicationID.HEAD)) {
      throw new IllegalArgumentException("Cannot get branch changes from HEAD");
    }

    ApplicationID headAppId = appId.asHead();
    Map<String, NCubeInfoDto> headMap = new TreeMap<>();

    Map<String, Object> searchChangedRecordOptions = new HashMap<>();
    searchChangedRecordOptions.put(SEARCH_CHANGED_RECORDS_ONLY, true);
    List<NCubeInfoDto> branchList = search(appId, null, null, searchChangedRecordOptions);

    Map<String, Object> options = new HashMap<>();
    options.put(SEARCH_ACTIVE_RECORDS_ONLY, false);
    List<NCubeInfoDto> headList = search(headAppId, null, null, options);

    List<NCubeInfoDto> list = new ArrayList<>();

    //  build map of head objects for reference.
    for (NCubeInfoDto info : headList) {
      headMap.put(info.name, info);
    }

    // Loop through changed (added, deleted, created, restored, updated) records
    for (NCubeInfoDto info : branchList) {
      long revision = (long) Converter.convert(info.revision, long.class);
      NCubeInfoDto head = headMap.get(info.name);

      if (head == null) {
        if (revision >= 0) {
          info.changeType = ChangeType.CREATED.getCode();
          list.add(info);
        }
      } else if (info.headSha1 == null) { //  we created this guy locally
        // someone added this one to the head already
        info.changeType = ChangeType.CONFLICT.getCode();
        list.add(info);
      } else {
        if (StringUtilities.equalsIgnoreCase(info.headSha1, head.sha1)) {
          if (StringUtilities.equalsIgnoreCase(info.sha1, info.headSha1)) {
            // only net change could be revision deleted or restored.  check head.
            long headRev = Long.parseLong(head.revision);

            if (headRev < 0 != revision < 0) {
              if (revision < 0) {
                info.changeType = ChangeType.DELETED.getCode();
              } else {
                info.changeType = ChangeType.RESTORED.getCode();
              }

              list.add(info);
            }
          } else {
            info.changeType = ChangeType.UPDATED.getCode();
            list.add(info);
          }
        } else {
          info.changeType = ChangeType.CONFLICT.getCode();
          list.add(info);
        }
      }
    }

    cacheCubes(appId, list);
    return list;
  }