001/******************************************************************************* 002 * Copyright 2017 The MIT Internet Trust Consortium 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 *******************************************************************************/ 016package org.mitre.data; 017 018import java.util.Collection; 019import java.util.HashSet; 020import java.util.Set; 021 022import org.slf4j.Logger; 023import org.slf4j.LoggerFactory; 024 025/** 026 * Abstract class for performing an operation on a potentially large 027 * number of items by paging through the items in discreet chunks. 028 * 029 * @param <T> the type parameter 030 * @author Colm Smyth. 031 */ 032public abstract class AbstractPageOperationTemplate<T> { 033 034 private static final Logger logger = LoggerFactory.getLogger(AbstractPageOperationTemplate.class); 035 036 private static int DEFAULT_MAX_PAGES = 1000; 037 private static long DEFAULT_MAX_TIME_MILLIS = 600000L; //10 Minutes 038 039 /** 040 * int specifying the maximum number of 041 * pages which should be fetched before 042 * execution should terminate 043 */ 044 private int maxPages; 045 046 /** 047 * long specifying the maximum execution time 048 * in milliseconds 049 */ 050 private long maxTime; 051 052 /** 053 * boolean specifying whether or not Exceptions 054 * incurred performing the operation should be 055 * swallowed during execution default true. 056 */ 057 private boolean swallowExceptions = true; 058 059 /** 060 * String that is used for logging in final tallies. 061 */ 062 private String operationName = ""; 063 064 065 /** 066 * default constructor which sets the value of 067 * maxPages and maxTime to DEFAULT_MAX_PAGES and 068 * DEFAULT_MAX_TIME_MILLIS respectively 069 */ 070 public AbstractPageOperationTemplate(String operationName){ 071 this(DEFAULT_MAX_PAGES, DEFAULT_MAX_TIME_MILLIS, operationName); 072 } 073 074 /** 075 * Instantiates a new AbstractPageOperationTemplate with the 076 * given maxPages and maxTime 077 * 078 * @param maxPages the maximum number of pages to fetch. 079 * @param maxTime the maximum execution time. 080 */ 081 public AbstractPageOperationTemplate(int maxPages, long maxTime, String operationName){ 082 this.maxPages = maxPages; 083 this.maxTime = maxTime; 084 this.operationName = operationName; 085 } 086 087 /** 088 * Execute the operation on each member of a page of results 089 * retrieved through the fetch method. the method will execute 090 * until either the maxPages or maxTime limit is reached or until 091 * the fetch method returns no more results. Exceptions thrown 092 * performing the operation on the item will be swallowed if the 093 * swallowException (default true) field is set true. 094 */ 095 public void execute(){ 096 logger.debug("[" + getOperationName() + "] Starting execution of paged operation. maximum time: " + maxTime + ", maximum pages: " + maxPages); 097 098 long startTime = System.currentTimeMillis(); 099 long executionTime = 0; 100 int i = 0; 101 102 int exceptionsSwallowedCount = 0; 103 int operationsCompleted = 0; 104 Set<String> exceptionsSwallowedClasses = new HashSet<String>(); 105 106 107 while (i< maxPages && executionTime < maxTime){ 108 Collection<T> page = fetchPage(); 109 if(page == null || page.size() == 0){ 110 break; 111 } 112 113 for (T item : page) { 114 try { 115 doOperation(item); 116 operationsCompleted++; 117 } catch (Exception e){ 118 if(swallowExceptions){ 119 exceptionsSwallowedCount++; 120 exceptionsSwallowedClasses.add(e.getClass().getName()); 121 logger.debug("Swallowing exception " + e.getMessage(), e); 122 } else { 123 logger.debug("Rethrowing exception " + e.getMessage()); 124 throw e; 125 } 126 } 127 } 128 129 i++; 130 executionTime = System.currentTimeMillis() - startTime; 131 } 132 133 finalReport(operationsCompleted, exceptionsSwallowedCount, exceptionsSwallowedClasses); 134 } 135 136 137 138 /** 139 * method responsible for fetching 140 * a page of items. 141 * 142 * @return the collection of items 143 */ 144 public abstract Collection<T> fetchPage(); 145 146 /** 147 * method responsible for performing desired 148 * operation on a fetched page item. 149 * 150 * @param item the item 151 */ 152 protected abstract void doOperation(T item); 153 154 /** 155 * Method responsible for final report of progress. 156 * @return 157 */ 158 protected void finalReport(int operationsCompleted, int exceptionsSwallowedCount, Set<String> exceptionsSwallowedClasses) { 159 if (operationsCompleted > 0 || exceptionsSwallowedCount > 0) { 160 logger.info("[" + getOperationName() + "] Paged operation run: completed " + operationsCompleted + "; swallowed " + exceptionsSwallowedCount + " exceptions"); 161 } 162 for(String className: exceptionsSwallowedClasses) { 163 logger.warn("[" + getOperationName() + "] Paged operation swallowed at least one exception of type " + className); 164 } 165 } 166 167 public int getMaxPages() { 168 return maxPages; 169 } 170 171 public void setMaxPages(int maxPages) { 172 this.maxPages = maxPages; 173 } 174 175 public long getMaxTime() { 176 return maxTime; 177 } 178 179 public void setMaxTime(long maxTime) { 180 this.maxTime = maxTime; 181 } 182 183 public boolean isSwallowExceptions() { 184 return swallowExceptions; 185 } 186 187 public void setSwallowExceptions(boolean swallowExceptions) { 188 this.swallowExceptions = swallowExceptions; 189 } 190 191 192 /** 193 * @return the operationName 194 */ 195 public String getOperationName() { 196 return operationName; 197 } 198 199 200 /** 201 * @param operationName the operationName to set 202 */ 203 public void setOperationName(String operationName) { 204 this.operationName = operationName; 205 } 206}