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}