timeSheetIds) {\n" +
+ " AllCalcDataManager origAcdMgr = getOriginalAllCalcDataManager(asgnmtMaster.getAsgnmt());\n" +
+ " try {\n" +
+ " WDateList dateList = new WDateList();\n" +
+ " for(TimeSheetIdentifier id : timeSheetIds) {\n" +
+ " dateList.add(id.getPpEnd());\n" +
+ " }\n" +
+ " updatedAllCalcDataManager = origAcdMgr.copyForUpdate(dateList);\n" +
+ " } catch (Exception e) {\n" +
+ " throw new InternalApplicationException(\"Cloning for \" + timeSheetIds, e);\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " private void validate(TimeSheetIdentifier timeSheetId) throws IllegalArgumentException {\n" +
+ " if (!timeSheetIdentifiers.contains(timeSheetId)) {\n" +
+ " throw new IllegalArgumentException(\"Invalid time sheet specified: \" + timeSheetId\n" +
+ " + \". not in \" + timeSheetIdentifiers);\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " /**\n" +
+ " * Obtains the original AllCalcDataManager for the given assignment Id. The assignment id must be given because in a\n" +
+ " * multiple assignment environment, a specific component needs to be requested, as originalAllCalcDataManager refers\n" +
+ " * to the aggregate assignment.\n" +
+ " *\n" +
+ " * @param asgnmtId The id of the assignment to load.\n" +
+ " * @return the original AllCalcDataManager for the given assignment Id\n" +
+ " */\n" +
+ " private AllCalcDataManager getOriginalAllCalcDataManager(GeneratedId asgnmtId) {\n" +
+ " if(originalAllCalcDataManager == null) {\n" +
+ " originalAllCalcDataManager = createOriginalAllCalcDataManager();\n" +
+ " updatedAllCalcDataManager = null;\n" +
+ " }\n" +
+ "\n" +
+ " if(asgnmtId.equals(originalAllCalcDataManager.getAsgnmtId())) {\n" +
+ " return originalAllCalcDataManager;\n" +
+ " }\n" +
+ "\n" +
+ " return originalAllCalcDataManager.getAllCalcDataManager(asgnmtId);\n" +
+ " }\n" +
+ "\n" +
+ " private AllCalcDataManager createOriginalAllCalcDataManager() {\n" +
+ " AllPolicies allPolicies = PolicyManager.getInstance().getAllPolicies();\n" +
+ " try {\n" +
+ " return AllCalcDataManager.createForAsgnmt(asgnmtMaster, allPolicies);\n" +
+ " }\n" +
+ " catch (Exception e) {\n" +
+ " throw new InternalApplicationException(\"Failed to load ACDM for assignment with id:\"+asgnmtMaster.getAsgnmt(), e);\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " /**\n" +
+ " * Obtains the updated AllCalcDataManager for the given assignment Id. The assignment id must be given because in a\n" +
+ " * multiple assignment environment, a specific component needs to be requested, as originalAllCalcDataManager refers\n" +
+ " * to the aggregate assignment.\n" +
+ " *\n" +
+ " * @param asgnmtId The id of the assignment to load.\n" +
+ " * @return the updated AllCalcDataManager for the given assignment Id\n" +
+ " */\n" +
+ " private AllCalcDataManager getUpdatedAllCalcDataManager(GeneratedId asgnmtId) {\n" +
+ " if(updatedAllCalcDataManager == null) {\n" +
+ " throw new InternalApplicationException(\"Attempted to use the updated all calc data manager before it was created.\");\n" +
+ " }\n" +
+ "\n" +
+ " if(asgnmtId.equals(updatedAllCalcDataManager.getAsgnmtId())) {\n" +
+ " return updatedAllCalcDataManager;\n" +
+ " }\n" +
+ "\n" +
+ " return updatedAllCalcDataManager.getAllCalcDataManager(asgnmtId);\n" +
+ " }\n" +
+ "\n" +
+ " /**\n" +
+ " * Obtains the updated AllCalcDataManager for the given assignment Id, if present. If not present, returns the\n" +
+ " * original ACDM instead. The assignment id must be given because in a\n" +
+ " * multiple assignment environment, a specific component needs to be requested, as originalAllCalcDataManager refers\n" +
+ " * to the aggregate assignment.\n" +
+ " *\n" +
+ " * @param asgnmtId The id of the assignment to load.\n" +
+ " * @return he updated AllCalcDataManager for the given assignment Id, if present. If not present, returns the\n" +
+ " * original ACDM instead.\n" +
+ " */\n" +
+ " AllCalcDataManager getLatestAllCalcDataManager(GeneratedId asgnmtId) {\n" +
+ " if (updatedAllCalcDataManager != null) {\n" +
+ " return getUpdatedAllCalcDataManager(asgnmtId);\n" +
+ " }\n" +
+ "\n" +
+ " return getOriginalAllCalcDataManager(asgnmtId);\n" +
+ " }\n" +
+ "\n" +
+ " //todo-ute: the method name is misleading as it may also create a new amended ACD if period is amendable by system on user's behalf\n" +
+ " private AllCalculationData getAllCalcData(AllCalcDataManager mgr, TimeSheetIdentifier id) {\n" +
+ " try {\n" +
+ " EmployeePeriodInfo epInfo = mgr.getEmployeePeriodInfo(id.getPpEnd());\n" +
+ " if (epInfo == null) {\n" +
+ " final PolicyID asgnmtPolicyProfile = mgr.getAsgnmtMaster().getPolicy_profile(id.getPpEnd());\n" +
+ " final PolicyID policyProfileId = (asgnmtPolicyProfile == null) ? PolicyID.EMPTY_POLICYID : asgnmtPolicyProfile;\n" +
+ " throw new InvalidPayPeriodException(\"EmployeePeriodInfo could not be obtained for employee \" +\n" +
+ " id.getEmployee() + \" and assignment \" + id.getAsgnmt() + \" and period end - \" + id.getPpEnd() + \". \" +\n" +
+ " \"Possibly because the employee is not active on that date or there are not enough initialized policy profile \" +\n" +
+ " \"periods for policy profile \" + policyProfileId );\n" +
+ " }\n" +
+ "\n" +
+ " TimeSheetCalculationInfo calcInfo = this.getCalculationInfo(mgr, id);\n" +
+ " AssignmentPeriodState asgnmtPeriodState = calcInfo.getAssignmentPeriodState();\n" +
+ " TimeSheetState timesheetState = calcInfo.getTimeSheetState();\n" +
+ " //If requesting EDIT_VERSION, check to see if one exists or that period is amendable by the logged in user\n" +
+ " if (id.getVersion() == EmployeePeriodVersionInfo.EDIT_VERSION && !epInfo.hasEditVersion()\n" +
+ " && !isPeriodAmendable(epInfo, mgr, asgnmtPeriodState, timesheetState) ) {\n" +
+ " throw new InternalApplicationException(\"An editable timesheet could not be obtained for employee \" +\n" +
+ " id.getEmployee() + \" and period end - \" + id.getPpEnd());\n" +
+ " }\n" +
+ "\n" +
+ " //Check to see if an editable ACD is in ACD manager's cache - if one exists, use it\n" +
+ " //An empty unmodifiable ACD for a prior period might be in cache if an ACD for prior timesheet is requested and\n" +
+ " //if that's the case, we want to still be able to create an amended ACD\n" +
+ " final AllCalculationData acd = mgr.getAllCalcData(id.getPpEnd(),id.getVersion(),AllCalculationData.FETCH_CACHED_ONLY);\n" +
+ " if (acd != null && !acd.isUnmodifiable()) {\n" +
+ " return acd;\n" +
+ " }\n" +
+ "\n" +
+ " //Create an amended ACD only if the period is amendable and the logged in user can amend it\n" +
+ " if (isAmendableBySystemForUser(epInfo, mgr, asgnmtPeriodState)) {\n" +
+ " final AllCalculationData amendedAllCalcData = mgr.createAmendedAllCalcData(epInfo, parms.getApp_user(), WDateTime.now());\n" +
+ " // No need to hook this up to approval change logic, since it is an in-memory change so far.\n" +
+ " //Recalculate the amended ACD so that timesheet exceptions (if any) get created on the amended timesheet\n" +
+ " amendedAllCalcData.recalc(mgr);\n" +
+ " }\n" +
+ "\n" +
+ " //By this time, the ACD mgr's ACD cache will have the amended ACD if one's created in this method\n" +
+ " //and we can retrieve the cached amended ACD or the ACD from the database\n" +
+ " return mgr.getAllCalcData(id.getPpEnd(), id.getVersion());\n" +
+ " } catch(InvalidPayPeriodException e) {\n" +
+ " //Rethrow InvalidPeriodExceptions--don't wrap them. We want them to be typed as InvalidPeriodExceptions.\n" +
+ " throw e;\n" +
+ " } catch (Exception e) {\n" +
+ " throw new InternalApplicationException(\"Timesheet could not be obtained for employee \" +\n" +
+ " id.getEmployee() + \", assignment \" + id.getAsgnmt()+ \", period end \" + id.getPpEnd() + \" and version \" + id.getVersion() + \".\", e);\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " /**\n" +
+ " * A period is amendable if one of the following is true:\n" +
+ " * \n" +
+ " * - {@link #isAmendableBySystemForUser(EmployeePeriodInfo, AllCalcDataManager, AssignmentPeriodState)\n" +
+ " * amendable by system on users's behalf}\n" +
+ " * - {@link TimeSheetState#isAmendable() amendable directly by user} generally by clicking \"Amend\" button on\n" +
+ " * prior active period with one or more closed timesheets\n" +
+ " * \n" +
+ " */\n" +
+ " private static boolean isPeriodAmendable(EmployeePeriodInfo epInfo, AllCalcDataManager mgr,\n" +
+ " AssignmentPeriodState asgnmtPeriodState, TimeSheetState timesheetState)\n" +
+ " throws SQLException, MultipleRowDbRecException {\n" +
+ " return timesheetState.isAmendable() || isAmendableBySystemForUser(epInfo, mgr, asgnmtPeriodState);\n" +
+ " }\n" +
+ "\n" +
+ " /**\n" +
+ " * A period is amendable by system on user's behalf (not the same as AUTO_AMEND or SYS_AMEND which are also created by\n" +
+ " * system) if user has amend rights for the specified period as of today and if specified period is a priod period\n" +
+ " * with no timesheet or it's a prior period timesheet with just system amended but not user amended\n" +
+ " * @param epInfo\n" +
+ " * @param mgr\n" +
+ " * @param asgnmtPeriodState\n" +
+ " * @return\n" +
+ " * @throws PolicyLookupException\n" +
+ " * @throws MultipleRowDbRecException\n" +
+ " * @throws SQLException\n" +
+ " * @see AssignmentPeriodState#isPeriodAmendable() \n" +
+ " * @see Approval_event_type#SYS_AMEND\n" +
+ " * @see Approval_event_type#AMEND\n" +
+ " */\n" +
+ " private static boolean isAmendableBySystemForUser(EmployeePeriodInfo epInfo, AllCalcDataManager mgr,\n" +
+ " AssignmentPeriodState asgnmtPeriodState)\n" +
+ " throws MultipleRowDbRecException, SQLException {\n" +
+ " return asgnmtPeriodState.isPeriodAmendable() && // TODO: this doesn't match the javadoc?\n" +
+ " ( mgr.isPriorModifiablePeriodWithNoTimesheet(epInfo) || epInfo.isPriorPeriodWithSysAmendVersionOnly() );\n" +
+ " }\n" +
+ "\n" +
+ " private AllCalculationData getOriginalAllCalcData(TimeSheetIdentifier timeSheetIdentifier) {\n" +
+ " return getAllCalcData(getOriginalAllCalcDataManager(timeSheetIdentifier.getAsgnmt()), timeSheetIdentifier);\n" +
+ " }\n" +
+ "\n" +
+ " private AllCalculationData getUpdatedAllCalcData(TimeSheetIdentifier timeSheetIdentifier) {\n" +
+ " return getAllCalcData(getUpdatedAllCalcDataManager(timeSheetIdentifier.getAsgnmt()), timeSheetIdentifier);\n" +
+ " }\n" +
+ "\n" +
+ "\n" +
+ " private Set getTimeSheetIdentifiers() {\n" +
+ " return timeSheetIdentifiers;\n" +
+ " }\n" +
+ "\n" +
+ " /**\n" +
+ " * Returns the pay period end for the period containing date provided, relative\n" +
+ " * to the defined periods for this assignment.\n" +
+ " *\n" +
+ " * @param aDateInUnknownPeriod any date we want to know the period for\n" +
+ " * @return never null\n" +
+ " */\n" +
+ " WDate getPpEndForDate(GeneratedId asgnmtId, WDate aDateInUnknownPeriod) {\n" +
+ " return TimeSchedUtils.getPpEndForDate(getOriginalAllCalcDataManager(asgnmtId), aDateInUnknownPeriod);\n" +
+ " }\n" +
+ "\n" +
+ " /**\n" +
+ " * Assign system_record_id's to any DbRec objecs in the collection which lack them.\n" +
+ " * @param dbRecList List of DbRec objects on which to check the system_record_id. Records in list will be modified.\n" +
+ " */\n" +
+ " static private void assignSystemRecordIds(ListWrapBase dbRecList) {\n" +
+ " for (Iterator iterator = dbRecList.getCollection().iterator(); iterator.hasNext();) {\n" +
+ " DbRec dbRec = (DbRec) iterator.next();\n" +
+ " assignSystemRecordId(dbRec);\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " /**\n" +
+ " * Assign system_record_id to a DbRec object, if it is not set or contains a temporary id.\n" +
+ " * @param dbRec DbRec objects on which to check/change the system_record_id.\n" +
+ " */\n" +
+ " static private void assignSystemRecordId(DbRec dbRec) {\n" +
+ " if (dbRec.getSystem_record_id() == null || dbRec.getSystem_record_id().requiresPermanentId()) {\n" +
+ " dbRec.setSystem_record_id(SystemId.getNewID());\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " /**\n" +
+ " * Removes the last approval event (employee approval) from approval event list from the AllCalculationData object of\n" +
+ " * {@link #updatedAllCalcDataManager}\n" +
+ " * @param timeSheetId - Used to get the {@link AllCalculationData} object from {@link #updatedAllCalcDataManager}\n" +
+ " */\n" +
+ " void undoEmployeeApproval(TimeSheetIdentifier timeSheetId) {\n" +
+ " final AllCalculationData updatedAllCalcData = getUpdatedAllCalcData(timeSheetId);\n" +
+ " updatedAllCalcData.getApproval_eventList().removeLastApprovalEvent();\n" +
+ " }\n" +
+ "\n" +
+ " /**\n" +
+ " * Update the current object to be able to be used for other timesheets for this assignment\n" +
+ " * @param parms\n" +
+ " * @param timeSheetIdentifiers\n" +
+ " */\n" +
+ " public void update(TimeEntryParmsPerAssignment parms, Set timeSheetIdentifiers) {\n" +
+ " setClassFields(parms, timeSheetIdentifiers);\n" +
+ " }\n" +
+ "\n" +
+ " private void setClassFields(TimeEntryParmsPerAssignment parms, Set timeSheetIdentifiers) {\n" +
+ " this.parms = parms;\n" +
+ " this.timeSheetIdentifiers = TimeEntryParmsPerAssignment.TIMESHEET_IDENTIFIER_TREESET_FACTORY.newInstance();\n" +
+ " this.timeSheetIdentifiers.addAll(timeSheetIdentifiers);\n" +
+ " }\n" +
+ "\n" +
+ " /**\n" +
+ " * Recalculates and Saves timesheets (if saveData == true) for timesheet ids in {@link #parms} that belong to this\n" +
+ " * assignment\n" +
+ " *\n" +
+ " * @param trans\n" +
+ " * @return AggregateTimeEntryTransactionResults for all saved timesheets\n" +
+ " * @throws Exception in case of fatal error during timesheet recalc & save e.g. errors while store data in DB etc.\n" +
+ " * This precludes ConcurrentUserModification exception. Any exception thrown from this method, eventually gets\n" +
+ " * escalated as transaction exception\n" +
+ " */\n" +
+ " public TimeEntryTransactionResults recalcAndSaveTimesheets(TimeEntryTransaction trans, boolean saveData) throws Exception {\n" +
+ " // TODO-13285: Remove this code and implement merging concurrently modified time sheets\n" +
+ " AllCalcDataManager tempAcdMgr = null;\n" +
+ " AggregateTimeEntryTransactionResults results = (AggregateTimeEntryTransactionResults) parms.getTimeEntryResultsFactory().newInstance();\n" +
+ " Set timeSheetIds = getAffectedTimeSheetIdentifiers(trans, parms);\n" +
+ " int retryCount = 0;\n" +
+ " boolean canRetryConcurrentModification = true;\n" +
+ " if (timeSheetIds.isEmpty()) {\n" +
+ " return results;\n" +
+ " }\n" +
+ "\n" +
+ " assert timeSheetIdentifiers.containsAll(timeSheetIds) : \"Attempted to recalculate a timesheet identifier not managed by this AssignmentManager.\";\n" +
+ " createUpdatedAllCalcDataManager(timeSheetIds);\n" +
+ "\n" +
+ " TransactionStatus timeSheetSaveStatus = null;\n" +
+ " Map applyResultsMap = new HashMap();\n" +
+ " for (TimeSheetIdentifier timeSheetId : timeSheetIds) {\n" +
+ " TimeSheetTransactionApplier applyResults = applyTransaction(timeSheetId, trans);\n" +
+ " if(applyResults.getApprovalEventType() != Approval_event_type.SAVE_SCHEDULE) {\n" +
+ " applyResultsMap.put(timeSheetId, applyResults);\n" +
+ " }\n" +
+ "\n" +
+ " recalc(timeSheetId, applyResults.getApprovalEventType());\n" +
+ "\n" +
+ " boolean isWithdrawal =\n" +
+ " (trans.findTransactionApprovalEvent(timeSheetId, Approval_event_type.WITHDRAWAL) != null);\n" +
+ " if (!isWithdrawal) {\n" +
+ " // There are 3 conditions identified in the following if statement that can prevent an \"action\" on the\n" +
+ " // current timesheet. The first is an exception that prevents save. In this case, we should never allow a\n" +
+ " // save of the timesheet to occur. The second condition is an exception that prevents submit. In this\n" +
+ " // condition, we should only allow a save if the user is not attempting to submit (apply an APPROVAL event).\n" +
+ " // The third case is errors that occur while applying transactions to the timesheet. An example of when this\n" +
+ " // may occur is if the same time sheet detail row is modified by 2 users. In all of these cases we prevent\n" +
+ " // the save operation from executing.\n" +
+ " // In the next condition where we check if an error has occurred, we remove the last approval event from the\n" +
+ " // timesheet if we did not save AND it was a submit (APPROVAL) event. Transactions only allow 1 event to be applied\n" +
+ " // to the timesheet. The transaction explicitly excludes SAVE_TIME_SHEET and SAVE_SCHEDULE events from being applied.\n" +
+ " // It relies on these events being applied only by the save operation itself. As a result, a maximum of 2 events may\n" +
+ " // be added to the timesheet as a result of the transaction (the event on the transaction if it was not a save event)\n" +
+ " // and the SAVE_TIME_SHEET or SAVE_SCHEDULE event added during the save operation.\n" +
+ "\n" +
+ " if (hasSubmitEvent(trans, timeSheetId) &&\n" +
+ " hasExceptionsPreventingSubmit(timeSheetId)) {\n" +
+ " timeSheetSaveStatus = getHighestPriorityError(timeSheetSaveStatus, Messages.EXCEPTIONS_PREVENT_SUBMIT);\n" +
+ " }\n" +
+ "\n" +
+ " if (hasExceptionsPreventingSave(timeSheetId)) {\n" +
+ " timeSheetSaveStatus = new TransactionStatusImpl(Transaction_status.ERROR,\n" +
+ " Messages.EXCEPTIONS_PREVENT_SAVE.getLabel());\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " if (applyResults.hasErrors()) {\n" +
+ " timeSheetSaveStatus = new TransactionStatusImpl(Transaction_status.ERROR,\n" +
+ " Messages.TIME_SHEET_EXCEPTION_JAVA_EXCEPTION .getLabel());\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ "\n" +
+ " boolean errorPreventsAction = (timeSheetSaveStatus != null);\n" +
+ "\n" +
+ "\n" +
+ " Approval_event lastOriginalApprovalEvent = null;\n" +
+ " for (TimeSheetIdentifier timeSheetId : timeSheetIds) {\n" +
+ " TimeSheetTransactionApplier applyResults = applyResultsMap.get(timeSheetId);\n" +
+ " try {\n" +
+ " while (saveData && canRetryConcurrentModification) {\n" +
+ " if (errorPreventsAction) {\n" +
+ " // If the changes were not saved due to errors, and this operation (transaction) was an employee timesheet submission,\n" +
+ " // remove the approval event from the unsaved ACD.\n" +
+ " // NOTE: We do not have to concern ourselves with removing SAVE_TIME_SHEET or SAVE_SCHEDULE events here as these events\n" +
+ " // are explicitly EXCLUDED in the TimeSheetTransactionApplier (not applied) and are applied by the save operation (which\n" +
+ " // has not executed if we made it here.\n" +
+ " if (hasSubmitEvent(trans, timeSheetId)) {\n" +
+ " undoEmployeeApproval(timeSheetId);\n" +
+ " }\n" +
+ " } else {\n" +
+ " save(timeSheetId, applyResults.getApprovalEventType());\n" +
+ " timeSheetSaveStatus = new TransactionStatusImpl(Transaction_status.SAVED,\n" +
+ " Messages.TIME_SHEET_SAVED.getLabel());\n" +
+ " postSave();\n" +
+ " }\n" +
+ " // All operations completed, so retries are no longer necessary\n" +
+ " canRetryConcurrentModification = false;\n" +
+ " }\n" +
+ " } catch (ConcurrentUserModificationException ex) {\n" +
+ " // Store the old AcdMgr, when a concurrent mod error due to another user modifying a time sheet occurs we want\n" +
+ " // to receive the same new approval events to ensure that the user is forced to reload the time sheet to continue.\n" +
+ " // TODO-13285: Remove the tempAcdMgr and make merging concurrently modified time sheets possible,\n" +
+ " // TODO-13285: requires considering multiple edge cases for the user interface\n" +
+ " if (tempAcdMgr == null) {\n" +
+ " tempAcdMgr = getOriginalAllCalcDataManager(asgnmtMaster.getAsgnmt());\n" +
+ " }\n" +
+ "\n" +
+ " if (retryCount == MAX_RETRIES) {\n" +
+ " // return the acdMgr to its previous state, this ensures the user must reload the time sheet\n" +
+ " // TODO-13285: Remove this code and implement merging concurrently modified time sheets\n" +
+ " originalAllCalcDataManager = tempAcdMgr;\n" +
+ " GeneratedId errorId = ServerErrorLogger.singleton.log(new ServerError(\"Exception saving time sheet\", ex,\n" +
+ " Program_source.SERVER_REQUEST, parms.getApp_user().getLogin_id()));\n" +
+ " cat.error(\"Unable to save time sheet. debug_error_log id:\" + errorId, ex);\n" +
+ " //Use timeSheetSaveStatus to send concurrent error message to user.\n" +
+ " errorPreventsAction = true;\n" +
+ " timeSheetSaveStatus = new TransactionStatusImpl(Transaction_status.ERROR,\n" +
+ " Messages.TIME_SHEET_EXCEPTION_CONCURRENT_USER.getLabel());\n" +
+ " // We have retried saving and have hit the MAX_RETRIES threshold, so stop retrying.\n" +
+ " canRetryConcurrentModification = false;\n" +
+ " } else {\n" +
+ " // On the first retry, we need to capture the last (most recent) approval event that existed on this timesheet prior to applying\n" +
+ " // any transactions because when we reload the \"original\" ACD to attempt to reapply the transactions we will lose track\n" +
+ " // of this (we'll pull in approval events for transactions that were saved in other sessions/processes). This is necessary to\n" +
+ " // allow more than 1 retry\n" +
+ " if (lastOriginalApprovalEvent == null) {\n" +
+ " lastOriginalApprovalEvent = getAllCalcData(getOriginalAllCalcDataManager(timeSheetId.getAsgnmt()), timeSheetId).getApproval_eventList().getLastApprovalEvent();\n" +
+ " }\n" +
+ " retryCount++;\n" +
+ " // attempt to reapply the transaction\n" +
+ " try {\n" +
+ " applyResults = attemptTimeSheetTransactionReapply(trans, timeSheetId, lastOriginalApprovalEvent);\n" +
+ " } catch (ConcurrentUserModificationException cume) {\n" +
+ " // return the acdMgr to its previous state, this ensures the user must reload the time sheet\n" +
+ " // TODO-13285: Remove this code and implement merging concurrently modified time sheets\n" +
+ " originalAllCalcDataManager = tempAcdMgr;\n" +
+ " // if we were unable to reapply transactions, no further retries are necessary (because it will just keep failing)\n" +
+ " // subsequent retries only succeed if we were able to reapply the new transactions successfully and still received\n" +
+ " // another ConcurrentUserModificationException (a second change occurred while applying transactions)\n" +
+ " canRetryConcurrentModification = false;\n" +
+ " errorPreventsAction = true;\n" +
+ " timeSheetSaveStatus = new TransactionStatusImpl(Transaction_status.ERROR,\n" +
+ " Messages.TIME_SHEET_EXCEPTION_CONCURRENT_USER.getLabel());\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ " results.add(getDiff(timeSheetId, trans, applyResults, timeSheetSaveStatus, !errorPreventsAction));\n" +
+ " }\n" +
+ " return results;\n" +
+ " }\n" +
+ "\n" +
+ " ";
+
+ String part2 = "\n" +
+ " private TransactionStatus getHighestPriorityError(TransactionStatus existingStatus, Message newError) {\n" +
+ " if(existingStatus == null) {\n" +
+ " return newError;\n" +
+ " }\n" +
+ " TransactionStatus timeSheetSaveStatus = new TransactionStatusImpl(Transaction_status.ERROR,\n" +
+ " );\n" +
+ " return timeSheetSaveStatus;\n" +
+ " }\n" +
+ "\n" +
+ " /**\n" +
+ " * Returns true if the given TimeEntryTransaction has a submit event for the given time sheet.\n" +
+ " * @param trans TimeEntryTransaction to search\n" +
+ " * @param timeSheetId time sheet to search\n" +
+ " * @return true if a submit event exists\n" +
+ " */\n" +
+ " private static boolean hasSubmitEvent(TimeEntryTransaction trans, TimeSheetIdentifier timeSheetId) {\n" +
+ " return trans.findTransactionApprovalEvent(timeSheetId, Approval_event_type.APPROVAL) != null;\n" +
+ " }\n" +
+ "\n" +
+ " /**\n" +
+ " * Resolves modifications that impact only non-\"input\" values on the timesheet to prevent them\n" +
+ " * from causing ConcurrentUserModificationExceptions on subsequent attempts at saving the data\n" +
+ " * @param trans time entry transaction to resolve\n" +
+ " * @param id time sheet ID\n" +
+ " * @param lastApprovalEvent the most recent approval event on the timesheet prior to any transactions\n" +
+ " * @return TimeSheetTransactionApplier if transactions were reapplied\n" +
+ " * @throws Exception on error loading the acd\n" +
+ " */\n" +
+ " private TimeSheetTransactionApplier attemptTimeSheetTransactionReapply(final TimeEntryTransaction trans, final TimeSheetIdentifier id, Approval_event lastApprovalEvent) throws Exception {\n" +
+ " // Clean out the ACDM cache to force reloads of the data from the database\n" +
+ " invalidateAllCalcDataManagers();\n" +
+ " AllCalculationData originalAcd = getAllCalcData(getOriginalAllCalcDataManager(id.getAsgnmt()), id);\n" +
+ " // Get any new approval events that exist in the database\n" +
+ " Approval_eventList newApprovalEvents = originalAcd.getApproval_eventList().getApprovalsSince(lastApprovalEvent);\n" +
+ " // Check if a \"concurrent save\" is possible given the events that have occurred\n" +
+ " if (newApprovalEvents.canConcurrentSave()) {\n" +
+ " //TODO:FIX\n" +
+ " //createUpdatedAllCalcDataManager(listOfAllIdsHere);\n" +
+ " TimeSheetTransactionApplier applyResults = applyTransaction(id, trans); // reapply transactions\n" +
+ " recalc(id, applyResults.getApprovalEventType()); // recalculate\n" +
+ " DbRecTime_sheet originalTimeSheet = originalAcd.getTime_sheet();\n" +
+ " DbRecTime_sheet updatedTimeSheet = getAllCalcData(getUpdatedAllCalcDataManager(id.getAsgnmt()), id).getTime_sheet();\n" +
+ " // Copy the system fields from the original time sheet to the updated time sheet because\n" +
+ " // we have determined at this point that the concurrent save is okay based on the difference\n" +
+ " // between these 2 time sheets. Copying these fields over will allow the save to complete\n" +
+ " // without causing a ConcurrentUserModificationException by making the update counter match.\n" +
+ " DbRecFieldCopier copier = new DbRecFieldCopier(DataDictionary.getCltnOfSystemFieldNames());\n" +
+ " copier.copyFields(originalTimeSheet, updatedTimeSheet);\n" +
+ " return applyResults;\n" +
+ " } else {\n" +
+ " // Otherwise, throw the exception\n" +
+ " throw new ConcurrentUserModificationException(\"Unable to save time sheet for assignment \" + originalAcd.getAsgnmtId() + \"due to concurrent changes.\");\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " /**\n" +
+ " * Returns a set of AssignmentManagers that are affected by changes in the given transaction and managed by this\n" +
+ " * TimeEntryManager.\n" +
+ " *\n" +
+ " * @return a set of AssignmentManagers that are affected by changes in the given transaction and managed by this\n" +
+ " * TimeEntryManager.\n" +
+ " */\n" +
+ " public Set getAffectedTimeSheetIdentifiers(TimeEntryTransaction transaction, TimeEntryParmsPerAssignment parms) {\n" +
+ " Set affectedTimeSheetIds = new HashSet();\n" +
+ " for(GeneratedId assignmentId : getAssignmentIds()) {\n" +
+ " for(TimeSheetIdentifier timeSheetId : parms.getTimeSheetIdentifiersForAssignment(assignmentId)) {\n" +
+ " if(!transaction.obtainAllRows(timeSheetId).isEmpty()) {\n" +
+ " affectedTimeSheetIds.add(timeSheetId);\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ " return affectedTimeSheetIds;\n" +
+ " }\n" +
+ "\n" +
+ " /**\n" +
+ " * Obtains all assignment id's that are managed by this AssignmentManager.\n" +
+ " *\n" +
+ " * @return all assignment id's that are managed by this AssignmentManager.\n" +
+ " */\n" +
+ " public Set getAssignmentIds() {\n" +
+ " Set assignmentIds = new HashSet();\n" +
+ " Asgnmt_masterList amList = asgnmtMaster.getCompAsgnmtMasters();\n" +
+ " for(Asgnmt_master am : amList) {\n" +
+ " assignmentIds.add(am.getAsgnmt());\n" +
+ " }\n" +
+ " return assignmentIds;\n" +
+ " }\n" +
+ "\n" +
+ " public GeneratedId getEmployee() {\n" +
+ " return asgnmtMaster.getEmployee();\n" +
+ " }\n" +
+ "\n" +
+ " /** Single or aggregate assignment master associated with this manager */\n" +
+ " private final Asgnmt_master asgnmtMaster;\n" +
+ "\n" +
+ " private TimeEntryParmsPerAssignment parms;\n" +
+ "\n" +
+ " /** Set of {@link TimeSheetIdentifier}'s which are managed by this object.\n" +
+ " * Composed of all the identifiers in the TimeEntryParms for this assignment and related components.\n" +
+ " */\n" +
+ " private Set timeSheetIdentifiers;\n" +
+ "\n" +
+ " /**\n" +
+ " * Original Single or Aggregate ACDM, as loaded from the database. ACD's in here are not modified.\n" +
+ " * This ACDM is lazily loaded, and should always be obtained from {@link #getOriginalAllCalcDataManager(GeneratedId)}.\n" +
+ " *\n" +
+ " * Null means that the ACDM has not yet been loaded from the database, and any attempts to use it should first\n" +
+ " * initialize it. Note: The meaning of null is different than updatedAllCalcDataManager's null meaning.\n" +
+ " */\n" +
+ " private AllCalcDataManager originalAllCalcDataManager = null;\n" +
+ "\n" +
+ " /**\n" +
+ " * Updated Single or Aggregate ACDM--a shallow copy of originalAllCalcDataManager. ACD's in here are modified by\n" +
+ " * TimeEntryTransactions. This ACDM is NOT lazily loaded--null means no changes present, and non-null means the\n" +
+ " * updated ACDM has been explicitly created.\n" +
+ " *\n" +
+ " * Null means that no changes have been made to the ACDM or data, and any attempts to use it should either throw a\n" +
+ " * descriptive error, or explicitly create the updatedAllCalcDataManager.\n" +
+ " * Note: The meaning of null is different than originalAllCalcDataManager's null meaning.\n" +
+ " */\n" +
+ " private AllCalcDataManager updatedAllCalcDataManager = null;\n" +
+ "\n" +
+ " private static final int MAX_RETRIES = 2;\n" +
+ "\n" +
+ " private static final Category cat=Category.getInstance(AssignmentManager.class.getName());\n" +
+ "}";
+ configureFromFileText("Foo.java", part1 + part2);
+
+ final PsiDocumentManager docManager = PsiDocumentManager.getInstance(ourProject);
+ final Document doc = docManager.getDocument(myFile);
+ doc.insertString(part1.length(), "/**");
+
+ boolean old = DebugUtil.CHECK;
+ DebugUtil.CHECK = true;
+ try {
+ docManager.commitAllDocuments();
+ }
+ finally {
+ DebugUtil.CHECK = old;
+ }
+ }
+}