예제 #1
0
  /** Restore a previously deleted n-cube. */
  public static void restoreCubes(ApplicationID appId, Object[] cubeNames, String username) {
    validateAppId(appId);
    appId.validateBranchIsNotHead();

    if (appId.isRelease()) {
      throw new IllegalArgumentException(
          ReleaseStatus.RELEASE + " cubes cannot be restored, app: " + appId);
    }

    if (ArrayUtilities.isEmpty(cubeNames)) {
      throw new IllegalArgumentException(
          "Error, empty array of cube names passed in to be restored.");
    }

    // Batch restore
    getPersister().restoreCubes(appId, cubeNames, username);

    // Load cache
    for (Object name : cubeNames) {
      if ((name instanceof String)) {
        String cubeName = (String) name;
        NCube.validateCubeName(cubeName);
        NCube ncube = getPersister().loadCube(appId, cubeName);
        addCube(appId, ncube);
      } else {
        throw new IllegalArgumentException("Non string name given for cube to restore: " + name);
      }
    }
  }
예제 #2
0
  /**
   * Update the passed in NCube. Only SNAPSHOT cubes can be updated.
   *
   * @param ncube NCube to be updated.
   * @return boolean true on success, false otherwise
   */
  public static boolean updateCube(ApplicationID appId, NCube ncube, String username) {
    validateAppId(appId);
    validateCube(ncube);

    if (appId.isRelease()) {
      throw new IllegalArgumentException(
          ReleaseStatus.RELEASE
              + " cubes cannot be updated, cube: "
              + ncube.getName()
              + ", app: "
              + appId);
    }

    appId.validateBranchIsNotHead();

    final String cubeName = ncube.getName();
    getPersister().updateCube(appId, ncube, username);
    ncube.setApplicationID(appId);

    if (CLASSPATH_CUBE.equalsIgnoreCase(
        cubeName)) { // If the sys.classpath cube is changed, then the entire class loader must be
                     // dropped.  It will be lazily rebuilt.
      clearCache(appId);
    }

    addCube(appId, ncube);
    broadcast(appId);
    return true;
  }
예제 #3
0
 /**
  * Get all of the versions that exist for the given ApplicationID (tenant and app).
  *
  * @return List<String> version numbers.
  */
 public static List<String> getAppVersions(
     String tenant, String app, String status, String branch) {
   ApplicationID.validateTenant(tenant);
   ApplicationID.validateApp(app);
   ApplicationID.validateBranch(branch);
   return getPersister().getAppVersions(tenant, app, status, branch);
 }
예제 #4
0
 /**
  * Rollback the passed in list of n-cubes. Each one will be returned to the state is was when the
  * branch was created. This is an insert cube (maintaining revision history) for each cube passed
  * in.
  */
 public static int rollbackCubes(ApplicationID appId, Object[] names, String username) {
   validateAppId(appId);
   appId.validateBranchIsNotHead();
   appId.validateStatusIsNotRelease();
   int count = getPersister().rollbackCubes(appId, names, username);
   clearCache(appId);
   return count;
 }
예제 #5
0
 /** Create a branch off of a SNAPSHOT for the given ApplicationIDs n-cubes. */
 public static int createBranch(ApplicationID appId) {
   validateAppId(appId);
   appId.validateBranchIsNotHead();
   appId.validateStatusIsNotRelease();
   int rows = getPersister().createBranch(appId);
   clearCache(appId);
   broadcast(appId);
   return rows;
 }
예제 #6
0
  public static boolean mergeAcceptMine(ApplicationID appId, String cubeName, String username) {
    validateAppId(appId);
    appId.validateBranchIsNotHead();
    appId.validateStatusIsNotRelease();

    boolean ret = getPersister().mergeAcceptMine(appId, cubeName, username);

    Map<String, Object> appCache = getCacheForApp(appId);
    appCache.remove(cubeName.toLowerCase());

    return ret;
  }
예제 #7
0
  public static void changeVersionValue(ApplicationID appId, String newVersion) {
    validateAppId(appId);

    if (appId.isRelease()) {
      throw new IllegalArgumentException(
          "Cannot change the version of a " + ReleaseStatus.RELEASE + " app, app: " + appId);
    }
    ApplicationID.validateVersion(newVersion);
    getPersister().changeVersionValue(appId, newVersion);
    clearCache(appId);
    clearCache(appId.asVersion(newVersion));
    broadcast(appId);
  }
예제 #8
0
  public static void clearCacheForBranches(ApplicationID appId) {
    synchronized (ncubeCache) {
      List<ApplicationID> list = new ArrayList<>();

      for (ApplicationID id : ncubeCache.keySet()) {
        if (id.cacheKey().startsWith(appId.branchAgnosticCacheKey())) {
          list.add(id);
        }
      }

      for (ApplicationID appId1 : list) {
        clearCache(appId1);
      }
    }
  }
예제 #9
0
  /** Duplicate the given n-cube specified by oldAppId and oldName to new ApplicationID and name, */
  public static void duplicate(
      ApplicationID oldAppId,
      ApplicationID newAppId,
      String oldName,
      String newName,
      String username) {
    validateAppId(oldAppId);
    validateAppId(newAppId);

    newAppId.validateBranchIsNotHead();

    if (newAppId.isRelease()) {
      throw new IllegalArgumentException(
          "Cubes cannot be duplicated into a "
              + ReleaseStatus.RELEASE
              + " version, cube: "
              + newName
              + ", app: "
              + newAppId);
    }

    NCube.validateCubeName(oldName);
    NCube.validateCubeName(newName);

    if (oldName.equalsIgnoreCase(newName) && oldAppId.equals(newAppId)) {
      throw new IllegalArgumentException(
          "Could not duplicate, old name cannot be the same as the new name when oldAppId matches newAppId, name: "
              + oldName
              + ", app: "
              + oldAppId);
    }

    getPersister().duplicateCube(oldAppId, newAppId, oldName, newName, username);

    if (CLASSPATH_CUBE.equalsIgnoreCase(
        newName)) { // If another cube is renamed into sys.classpath,
      // then the entire class loader must be dropped (and then lazily rebuilt).
      clearCache(newAppId);
    } else {
      Map<String, Object> appCache = getCacheForApp(newAppId);
      appCache.remove(newName.toLowerCase());
    }

    broadcast(newAppId);
  }
예제 #10
0
 /** Perform release (SNAPSHOT to RELEASE) for the given ApplicationIDs n-cubes. */
 public static int releaseCubes(ApplicationID appId, String newSnapVer) {
   validateAppId(appId);
   ApplicationID.validateVersion(newSnapVer);
   int rows = getPersister().releaseCubes(appId, newSnapVer);
   clearCacheForBranches(appId);
   // TODO:  Does broadcast need to send all branches that have changed as a result of this?
   broadcast(appId);
   return rows;
 }
예제 #11
0
  public static boolean renameCube(
      ApplicationID appId, String oldName, String newName, String username) {
    validateAppId(appId);
    appId.validateBranchIsNotHead();

    if (appId.isRelease()) {
      throw new IllegalArgumentException(
          "Cannot rename a "
              + ReleaseStatus.RELEASE
              + " cube, cube: "
              + oldName
              + ", app: "
              + appId);
    }

    NCube.validateCubeName(oldName);
    NCube.validateCubeName(newName);

    if (oldName.equalsIgnoreCase(newName)) {
      throw new IllegalArgumentException(
          "Could not rename, old name cannot be the same as the new name, name: "
              + oldName
              + ", app: "
              + appId);
    }

    boolean result = getPersister().renameCube(appId, oldName, newName, username);

    if (CLASSPATH_CUBE.equalsIgnoreCase(oldName)
        || CLASSPATH_CUBE.equalsIgnoreCase(
            newName)) { // If the sys.classpath cube is renamed, or another cube is renamed into
                        // sys.classpath,
      // then the entire class loader must be dropped (and then lazily rebuilt).
      clearCache(appId);
    } else {
      Map<String, Object> appCache = getCacheForApp(appId);
      appCache.remove(oldName.toLowerCase());
      appCache.remove(newName.toLowerCase());
    }

    broadcast(appId);
    return result;
  }
예제 #12
0
  static boolean deleteCubes(
      ApplicationID appId, Object[] cubeNames, boolean allowDelete, String username) {
    validateAppId(appId);
    if (!allowDelete) {
      if (appId.isRelease()) {
        throw new IllegalArgumentException(
            ReleaseStatus.RELEASE + " cubes cannot be hard-deleted, app: " + appId);
      }
    }

    if (getPersister().deleteCubes(appId, cubeNames, allowDelete, username)) {
      Map<String, Object> appCache = getCacheForApp(appId);
      for (int i = 0; i < cubeNames.length; i++) {
        appCache.remove(((String) cubeNames[i]).toLowerCase());
      }
      broadcast(appId);
      return true;
    }
    return false;
  }
예제 #13
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;
  }
예제 #14
0
 public static boolean deleteBranch(ApplicationID appId) {
   appId.validateBranchIsNotHead();
   return getPersister().deleteBranch(appId);
 }
예제 #15
0
 /**
  * Delete the named NCube from the database
  *
  * @param cubeNames Object[] of String cube names to be deleted (soft deleted)
  */
 public static boolean deleteCubes(ApplicationID appId, Object[] cubeNames, String username) {
   appId.validateBranchIsNotHead();
   return deleteCubes(appId, cubeNames, false, username);
 }
예제 #16
0
 public static Set<String> getBranches(String tenant) {
   ApplicationID.validateTenant(tenant);
   return getPersister().getBranches(tenant);
 }
예제 #17
0
  public static ApplicationID getApplicationID(
      String tenant, String app, Map<String, Object> coord) {
    ApplicationID.validateTenant(tenant);
    ApplicationID.validateApp(tenant);

    if (coord == null) {
      coord = new HashMap<>();
    }

    NCube bootCube = getCube(ApplicationID.getBootVersion(tenant, app), SYS_BOOTSTRAP);

    if (bootCube == null) {
      throw new IllegalStateException(
          "Missing " + SYS_BOOTSTRAP + " cube in the 0.0.0 version for the app: " + app);
    }

    ApplicationID bootAppId = (ApplicationID) bootCube.getCell(coord);
    String version = bootAppId.getVersion();
    String status = bootAppId.getStatus();
    String branch = bootAppId.getBranch();

    if (!tenant.equalsIgnoreCase(bootAppId.getTenant())) {
      LOG.warn(
          "sys.bootstrap cube for tenant '"
              + tenant
              + "', app '"
              + app
              + "' is returning a different tenant '"
              + bootAppId.getTenant()
              + "' than requested. Using '"
              + tenant
              + "' instead.");
    }

    if (!app.equalsIgnoreCase(bootAppId.getApp())) {
      LOG.warn(
          "sys.bootstrap cube for tenant '"
              + tenant
              + "', app '"
              + app
              + "' is returning a different app '"
              + bootAppId.getApp()
              + "' than requested. Using '"
              + app
              + "' instead.");
    }

    return new ApplicationID(tenant, app, version, status, branch);
  }
예제 #18
0
 // ---------------------------------------- Validation APIs
 // --------------------------------------------------------
 static void validateAppId(ApplicationID appId) {
   if (appId == null) {
     throw new IllegalArgumentException("ApplicationID cannot be null");
   }
   appId.validate();
 }
예제 #19
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;
  }
예제 #20
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;
  }