/** 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); } } }
/** * 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; }
/** * 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); }
/** * 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; }
/** 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; }
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; }
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); }
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); } } }
/** 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); }
/** 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; }
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; }
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; }
/** * 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; }
public static boolean deleteBranch(ApplicationID appId) { appId.validateBranchIsNotHead(); return getPersister().deleteBranch(appId); }
/** * 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); }
public static Set<String> getBranches(String tenant) { ApplicationID.validateTenant(tenant); return getPersister().getBranches(tenant); }
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); }
// ---------------------------------------- Validation APIs // -------------------------------------------------------- static void validateAppId(ApplicationID appId) { if (appId == null) { throw new IllegalArgumentException("ApplicationID cannot be null"); } appId.validate(); }
/** * 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; }
/** * 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; }