/** Fetch the classloader for the given ApplicationID. */ static URLClassLoader getUrlClassLoader(ApplicationID appId, Map input) { NCube cpCube = getCube(appId, CLASSPATH_CUBE); if (cpCube == null) { // No sys.classpath cube exists, just create regular GroovyClassLoader with no // URLs set into it. // Scope the GroovyClassLoader per ApplicationID return getLocalClassloader(appId); } final String envLevel = SystemUtilities.getExternalVariable("ENV_LEVEL"); if (!input.containsKey("env") && StringUtilities.hasContent( envLevel)) { // Add in the 'ENV_LEVEL" environment variable when looking up sys.* cubes, // if there was not already an entry for it. input.put("env", envLevel); } if (!input.containsKey("username")) { // same as ENV_LEVEL, add it in if not already there. input.put("username", System.getProperty("user.name")); } Object urlCpLoader = cpCube.getCell(input); if (urlCpLoader instanceof URLClassLoader) { return (URLClassLoader) urlCpLoader; } throw new IllegalStateException( "If the sys.classpath cube exists it must return a URLClassLoader."); }
/** Associate Advice to all n-cubes that match the passed in regular expression. */ public static void addAdvice(ApplicationID appId, String wildcard, Advice advice) { validateAppId(appId); ConcurrentMap<String, Advice> current = advices.get(appId); if (current == null) { current = new ConcurrentHashMap<>(); ConcurrentMap<String, Advice> mapRef = advices.putIfAbsent(appId, current); if (mapRef != null) { current = mapRef; } } current.put(advice.getName() + '/' + wildcard, advice); // Apply newly added advice to any fully loaded (hydrated) cubes. String regex = StringUtilities.wildcardToRegexString(wildcard); Map<String, Object> cubes = getCacheForApp(appId); for (Object value : cubes.values()) { if (value instanceof NCube) { // apply advice to hydrated cubes NCube ncube = (NCube) value; Axis axis = ncube.getAxis("method"); addAdviceToMatchedCube(advice, regex, ncube, axis); } } }
/** * Apply existing advices loaded into the NCubeManager, to the passed in n-cube. This allows * advices to be added first, and then let them be applied 'on demand' as an n-cube is loaded * later. * * @param appId ApplicationID * @param ncube NCube to which all matching advices will be applied. */ private static void applyAdvices(ApplicationID appId, NCube ncube) { final Map<String, Advice> appAdvices = advices.get(appId); if (MapUtilities.isEmpty(appAdvices)) { return; } for (Map.Entry<String, Advice> entry : appAdvices.entrySet()) { final Advice advice = entry.getValue(); final String wildcard = entry.getKey().replace(advice.getName() + '/', ""); final String regex = StringUtilities.wildcardToRegexString(wildcard); final Axis axis = ncube.getAxis("method"); addAdviceToMatchedCube(advice, regex, ncube, axis); } }
public static String resolveRelativeUrl(ApplicationID appId, String relativeUrl) { validateAppId(appId); if (StringUtilities.isEmpty(relativeUrl)) { throw new IllegalArgumentException( "Cannot resolve relative url - relative url cannot be null or empty string."); } final String loUrl = relativeUrl.toLowerCase(); if (loUrl.startsWith("http:") || loUrl.startsWith("https:") || loUrl.startsWith("file:")) { return relativeUrl; } URLClassLoader classLoader = getUrlClassLoader(appId, new HashMap()); URL absUrl = classLoader.getResource(relativeUrl); return absUrl != null ? absUrl.toString() : null; }
public static Map<String, Object> getSystemParams() { final ConcurrentMap<String, Object> params = systemParams; if (params != null) { return params; } synchronized (NCubeManager.class) { if (systemParams == null) { String jsonParams = SystemUtilities.getExternalVariable(NCUBE_PARAMS); systemParams = new ConcurrentHashMap<>(); if (StringUtilities.hasContent(jsonParams)) { try { systemParams = new ConcurrentHashMap<>(JsonReader.jsonToMaps(jsonParams)); } catch (Exception e) { LOG.warn("Parsing of NCUBE_PARAMS failed. " + jsonParams); } } } } return systemParams; }
/** * 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; }
/** * 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; }