package wowodc.background.tasks; import java.text.DecimalFormat; import java.text.Format; import java.util.concurrent.Callable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import wowodc.background.utilities.Utilities; import wowodc.eof.ResultItem; import wowodc.eof.TaskInfo; import com.webobjects.eocontrol.EOEditingContext; import com.webobjects.eocontrol.EOGlobalID; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSTimestamp; import er.extensions.concurrency.ERXTask; import er.extensions.concurrency.IERXPercentComplete; import er.extensions.concurrency.IERXStoppable; import er.extensions.eof.ERXFetchSpecification; import er.extensions.eof.ERXFetchSpecificationBatchIterator; import er.extensions.foundation.IERXStatus; /** * This task iterates thru a the ResultItems related to a TaskInfo argument. * * For large data sets, the use of a {@link ERXFetchSpecificationBatchIterator} and replacing * the {@link EOEditingContext} for each batch can help * Factorial and factorial primes. * http://en.wikipedia.org/wiki/Factorial_prime * * @author kieran */ public class T06EOFFactorialUpdateTask extends ERXTask<EOGlobalID> implements Callable<EOGlobalID>, IERXStatus , IERXPercentComplete, IERXStoppable { private static final Logger log = LoggerFactory.getLogger(T06EOFFactorialUpdateTask.class); // Duration of the example task in milliseconds // Random between 5 and 15 seconds private final long DURATION = Utilities.sharedRandom().nextInt(10001) + 5000; // Task elapsed time in milliseconds private long _elapsedTime = 0l; // Value between 0.0 and 1.0 indicating the task's percentage complete private double _percentComplete = 0.0d; // A message indicating current status private String _status = "Starting..."; private long _countCompleted = 0; private long _totalCount = 0; private volatile boolean _isStopped = false; public T06EOFFactorialUpdateTask(TaskInfo taskInfo) { if (taskInfo.isNewObject()) { throw new IllegalArgumentException("taskInfo must be already saved to the database before this task can be run"); } // Check if workflow state is appropriate if (!taskInfo.workflowState().equals(TaskInfo.WORKFLOW_PRIME_CHECKED)) { throw new IllegalStateException("The taskInfo must be in the " + TaskInfo.WORKFLOW_PRIME_CHECKED + " before it can begin Factorial processing."); } // Grab GID reference before the task is started. _taskInfoGID = taskInfo.editingContext().globalIDForObject(taskInfo); } private final EOGlobalID _taskInfoGID; @Override public EOGlobalID _call() { _elapsedTime = 0; Format wholeNumberFormatter = new DecimalFormat("#,##0"); long startTime = System.currentTimeMillis(); // Note we use the superclass convenience method here. EOEditingContext ec = newEditingContext(); ec.lock(); try { // Fetch the TaskInfo TaskInfo taskInfo = (TaskInfo) ec.faultForGlobalID(_taskInfoGID, ec); _totalCount = taskInfo.countResultItems().longValue(); // Task start time // This is a demo, so we are going to replace the prime processing times with the factorial processing times taskInfo.setStartTime(new NSTimestamp(startTime)); // For demo purposes we will use batches and EC recycling, which would be common for processing huge data sets ERXFetchSpecification<ResultItem> fs = taskInfo.fetchSpecificationForResultItems(); // Batch iterator ERXFetchSpecificationBatchIterator<ResultItem> fsIterator = new ERXFetchSpecificationBatchIterator<>(fs, ec); // Loop for a period of time while (fsIterator.hasNext() && !_isStopped) { @SuppressWarnings("unchecked") NSArray<ResultItem> batch = fsIterator.nextBatch(); for (ResultItem resultItem : batch) { resultItem.setWorkflowState(ResultItem.WORKFLOW_CHECKING_FACTORIAL); performFactorialProcessing(resultItem); resultItem.setWorkflowState(ResultItem.WORKFLOW_PROCESSING_COMPLETE); ec.saveChanges(); _elapsedTime = System.currentTimeMillis() - startTime; // Update progress variables _countCompleted++; _percentComplete = (double)(_countCompleted) / (double)_totalCount; _status = wholeNumberFormatter.format(_countCompleted) + " numbers checked for factorial proximity"; if (_isStopped) { break; } } // Swap in a fresh EC for the next batch to help with memory management EOEditingContext freshEC = newEditingContext(); ec.unlock(); ec = freshEC; freshEC.lock(); fsIterator.setEditingContext(ec); // We need to refault taskInfo into the new EC after swapping taskInfo = (TaskInfo) ec.faultForGlobalID(_taskInfoGID, ec); } // Complete the stats taskInfo.setEndTime(new NSTimestamp()); taskInfo.setWorkflowState(TaskInfo.WORKFLOW_PROCESSING_COMPLETE); long duration = taskInfo.endTime().getTime() - taskInfo.startTime().getTime(); taskInfo.setDuration(duration); ec.saveChanges(); } finally { ec.unlock(); } return _taskInfoGID; } /** * Finds the closest factorial number and corresponding factor and updates resultItem. * If the resultItem was a prime number, we check if it is a Factorial Prime. * http://en.wikipedia.org/wiki/Factorial_prime * * @param resultItem */ private void performFactorialProcessing(ResultItem resultItem) { long numberToCheck = resultItem.numberToCheck().longValue(); long factor = 1; long factorial = 1; long distance = Math.abs(numberToCheck - factorial); long newDistance = 0; do { factor++; factorial = factorial * factor; newDistance = Math.abs(numberToCheck - factorial); log.debug("factor: {}; factorial: {}; distance: {}; new Distance: {}", factor, factorial, distance, newDistance); } while (newDistance < distance); // Upon exiting the loop, we will have gone one factor too far factorial = factorial / factor; factor--; resultItem.setClosestFactorial(factorial); resultItem.setFactorNumber((int)factor); // Check if Factorial prime if (resultItem.isPrime().booleanValue()) { if (Math.abs(numberToCheck - factorial) == 1) { // We have a Factorial Prime number resultItem.setIsFactorialPrime(Boolean.TRUE); } } } /* (non-Javadoc) * @see er.extensions.concurrency.IERXPercentComplete#percentComplete() */ public Double percentComplete() { return _percentComplete; } /* (non-Javadoc) * @see er.extensions.foundation.IERXStatus#status() */ public String status() { return _status; } /* (non-Javadoc) * @see er.extensions.concurrency.IERXStoppable#stop() */ public void stop() { log.info("The task was stopped by the user."); _isStopped = true; } }