class CrawlLimitEnforcer implements ApplicationListener<ApplicationEvent> {

    /**
     * Maximum number of bytes to download. Once this number is exceeded 
     * the crawler will stop. A value of zero means no upper limit.
     */
    protected long maxBytesDownload = 0L;
    public long getMaxBytesDownload() {
        return maxBytesDownload;
    }
    public void setMaxBytesDownload(long maxBytesDownload) {
        this.maxBytesDownload = maxBytesDownload;
    }

    protected long maxNovelBytes = 0L;
    public long getMaxNovelBytes() {
        return maxNovelBytes;
    }
    /**
     * Maximum number of uncompressed payload bytes to write to WARC response or
     * resource records. Once this number is exceeded the crawler will stop. A
     * value of zero means no upper limit.
     */
    public void setMaxNovelBytes(long maxNovelBytes) {
        this.maxNovelBytes = maxNovelBytes;
    }

    protected long maxNovelUrls = 0L;
    public long getMaxNovelUrls() {
        return maxNovelUrls;
    }
    /**
     * Maximum number of novel (not deduplicated) urls to download. Once this
     * number is exceeded the crawler will stop. A value of zero means no upper
     * limit.
     */
    public void setMaxNovelUrls(long maxNovelUrls) {
        this.maxNovelUrls = maxNovelUrls;
    }

    protected long maxWarcNovelUrls = 0L;
    public long getMaxWarcNovelUrls() {
        return maxWarcNovelUrls;
    }
    /**
     * Maximum number of urls to write to WARC response or resource records.
     * Once this number is exceeded the crawler will stop. A value of zero means
     * no upper limit.
     */
    public void setMaxWarcNovelUrls(long maxWarcNovelUrls) {
        this.maxWarcNovelUrls = maxWarcNovelUrls;
    }

    protected long maxWarcNovelBytes = 0L;
    public long getMaxWarcNovelBytes() {
        return maxWarcNovelBytes;
    }
    
    /**
     * Maximum number of novel (not deduplicated) bytes to write to WARC
     * response or resource records. Once this number is exceeded the crawler
     * will stop. A value of zero means no upper limit.
     */
    public void setMaxWarcNovelBytes(long maxWarcNovelBytes) {
        this.maxWarcNovelBytes = maxWarcNovelBytes;
    }

    /**
     * Maximum number of documents to download. Once this number is exceeded the 
     * crawler will stop. A value of zero means no upper limit.
     */
    protected long maxDocumentsDownload = 0L; 
    public long getMaxDocumentsDownload() {
        return maxDocumentsDownload;
    }
    public void setMaxDocumentsDownload(long maxDocumentsDownload) {
        this.maxDocumentsDownload = maxDocumentsDownload;
    }

    /**
     * Maximum amount of time to crawl (in seconds). Once this much time has 
     * elapsed the crawler will stop. A value of zero means no upper limit.
     */
    protected long maxTimeSeconds = 0L;
    public long getMaxTimeSeconds() {
        return maxTimeSeconds;
    }
    public void setMaxTimeSeconds(long maxTimeSeconds) {
        this.maxTimeSeconds = maxTimeSeconds;
    }

    protected CrawlController controller;
    public CrawlController getCrawlController() {
        return this.controller;
    }
    @Autowired
    public void setCrawlController(CrawlController controller) {
        this.controller = controller;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if(event instanceof StatSnapshotEvent) {
            CrawlStatSnapshot snapshot = ((StatSnapshotEvent)event).getSnapshot();
            checkForLimitsExceeded(snapshot);
        }
    }

    protected void checkForLimitsExceeded(CrawlStatSnapshot snapshot) {<FILL_FUNCTION_BODY>}
}

        if (maxBytesDownload > 0 && snapshot.bytesProcessed >= maxBytesDownload) {
            controller.requestCrawlStop(CrawlStatus.FINISHED_DATA_LIMIT);
        } else if (maxNovelBytes > 0 && snapshot.novelBytes >= maxNovelBytes) {
            controller.requestCrawlStop(CrawlStatus.FINISHED_DATA_LIMIT);
        } else if (maxWarcNovelBytes > 0 && snapshot.warcNovelBytes >= maxWarcNovelBytes) {
            controller.requestCrawlStop(CrawlStatus.FINISHED_DATA_LIMIT);
        } else if (maxDocumentsDownload > 0
                && snapshot.downloadedUriCount >= maxDocumentsDownload) {
            controller.requestCrawlStop(CrawlStatus.FINISHED_DOCUMENT_LIMIT);
        } else if (maxNovelUrls > 0
                && snapshot.novelUriCount >= maxNovelUrls) {
            controller.requestCrawlStop(CrawlStatus.FINISHED_DOCUMENT_LIMIT);
        } else if (maxWarcNovelUrls > 0
                && snapshot.warcNovelUriCount >= maxWarcNovelUrls) {
            controller.requestCrawlStop(CrawlStatus.FINISHED_DOCUMENT_LIMIT);
        } else if (maxTimeSeconds > 0 
                && snapshot.elapsedMilliseconds >= maxTimeSeconds * 1000) {
            controller.requestCrawlStop(CrawlStatus.FINISHED_TIME_LIMIT);
        }
    
<no_super_class>