class RuntimeLimitEnforcer extends Processor {

    @SuppressWarnings("unused")
    private static final long serialVersionUID = 3L;
    
    protected static Logger logger = Logger.getLogger(
            RuntimeLimitEnforcer.class.getName());


    /**
     * The action that the processor takes once the runtime has elapsed.
     */
    public static enum Operation { 

        /**
         * Pauses the crawl. A change (increase) to the runtime duration will
         * make it pausible to resume the crawl. Attempts to resume the crawl
         * without modifying the run time will cause it to be immediately paused
         * again.
         */
        PAUSE, 
        
        /**
         * Terminates the job. Equivalent to using the max-time setting on the
         * CrawlController.
         */
        TERMINATE, 
        
        /**
         * Blocks each URI with an -5002 (blocked by custom processor) fetch
         * status code. This will cause all the URIs queued to wind up in the
         * crawl.log.
         */
        BLOCK_URIS 
    };
    
    /**
     * The amount of time, in seconds, that the crawl will be allowed to run
     * before this processor performs it's 'end operation.'
     */
    protected long runtimeSeconds = 24*60*60L; // 1 day
    public long getRuntimeSeconds() {
        return this.runtimeSeconds;
    }
    public void setRuntimeSeconds(long secs) {
        this.runtimeSeconds = secs;
    }

    /**
     * The action that the processor takes once the runtime has elapsed.
     * <p>
     * Operation: Pause job - Pauses the crawl. A change (increase) to the
     * runtime duration will make it pausible to resume the crawl. Attempts to
     * resume the crawl without modifying the run time will cause it to be
     * immediately paused again.
     * <p>
     * Operation: Terminate job - Terminates the job. Equivalent to using the
     * max-time setting on the CrawlController.
     * <p>
     * Operation: Block URIs - Blocks each URI with an -5002 (blocked by custom
     * processor) fetch status code. This will cause all the URIs queued to wind
     * up in the crawl.log.
     */
    protected Operation expirationOperation = Operation.PAUSE;
    public Operation getExpirationOperation() {
        return this.expirationOperation;
    }
    public void setExpirationOperation(Operation op) {
        this.expirationOperation = op; 
    }

    protected CrawlController controller;
    public CrawlController getCrawlController() {
        return this.controller;
    }
    @Autowired
    public void setCrawlController(CrawlController controller) {
        this.controller = controller;
    }
    
    protected StatisticsTracker statisticsTracker;
    public StatisticsTracker getStatisticsTracker() {
        return this.statisticsTracker;
    }
    @Autowired
    public void setStatisticsTracker(StatisticsTracker statisticsTracker) {
        this.statisticsTracker = statisticsTracker;
    }
    
    public RuntimeLimitEnforcer() {
        super();
    }

    
    @Override
    protected boolean shouldProcess(CrawlURI puri) {
        return puri instanceof CrawlURI;
    }

    
    @Override 
    protected void innerProcess(CrawlURI curi) {
        throw new AssertionError();
    }
    
    @Override
    protected ProcessResult innerProcessResult(CrawlURI curi)
    throws InterruptedException {<FILL_FUNCTION_BODY>}
}

        CrawlController controller = getCrawlController();
        StatisticsTracker stats = getStatisticsTracker();
        long allowedRuntimeMs = getRuntimeSeconds() * 1000L;
        long currentRuntimeMs = stats.getCrawlElapsedTime();
        if(currentRuntimeMs > allowedRuntimeMs){
            Operation op = getExpirationOperation();
            if(op != null){
                if (op.equals(Operation.PAUSE)) {
                    controller.requestCrawlPause();
                } else if (op.equals(Operation.TERMINATE)){
                    controller.requestCrawlStop(CrawlStatus.FINISHED_TIME_LIMIT);
                } else if (op.equals(Operation.BLOCK_URIS)) {
                    curi.setFetchStatus(S_BLOCKED_BY_RUNTIME_LIMIT);
                    curi.getAnnotations().add("Runtime exceeded " + allowedRuntimeMs + 
                            "ms");
                    return ProcessResult.FINISH;
                }
            } else {
                logger.log(Level.SEVERE,"Null value for end-operation " + 
                        " when processing " + curi.toString());
            }
        }
        return ProcessResult.PROCEED;
    
<methods>public non-sealed void <init>() ,public void doCheckpoint(org.archive.checkpointing.Checkpoint) throws java.io.IOException,public void finishCheckpoint(org.archive.checkpointing.Checkpoint) ,public static java.lang.String flattenVia(org.archive.modules.CrawlURI) ,public java.lang.String getBeanName() ,public boolean getEnabled() ,public org.archive.spring.KeyedProperties getKeyedProperties() ,public static long getRecordedSize(org.archive.modules.CrawlURI) ,public org.archive.modules.deciderules.DecideRule getShouldProcessRule() ,public long getURICount() ,public static boolean hasHttpAuthenticationCredential(org.archive.modules.CrawlURI) ,public boolean isRunning() ,public static boolean isSuccess(org.archive.modules.CrawlURI) ,public org.archive.modules.ProcessResult process(org.archive.modules.CrawlURI) throws java.lang.InterruptedException,public java.lang.String report() ,public void setBeanName(java.lang.String) ,public void setEnabled(boolean) ,public void setRecoveryCheckpoint(org.archive.checkpointing.Checkpoint) ,public void setShouldProcessRule(org.archive.modules.deciderules.DecideRule) ,public void start() ,public void startCheckpoint(org.archive.checkpointing.Checkpoint) ,public void stop() <variables>protected java.lang.String beanName,protected boolean isRunning,protected org.archive.spring.KeyedProperties kp,protected org.archive.checkpointing.Checkpoint recoveryCheckpoint,protected java.util.concurrent.atomic.AtomicLong uriCount