/** * 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; }
/** 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); } } }
/** * 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; }
/** 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); }
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; }
/** * 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; }
/** * 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; }
/** * 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 boolean deleteBranch(ApplicationID appId) { appId.validateBranchIsNotHead(); return getPersister().deleteBranch(appId); }