001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.monitoring.snapshot;
018    
019    import java.sql.Connection;
020    import java.sql.DriverManager;
021    import java.sql.ResultSet;
022    import java.sql.Statement;
023    import java.text.SimpleDateFormat;
024    import java.util.ArrayList;
025    import java.util.Calendar;
026    import java.util.Date;
027    import java.util.HashMap;
028    import java.util.Iterator;
029    import java.util.TreeMap;
030    
031    import javax.sql.DataSource;
032    
033    import org.apache.commons.logging.Log;
034    import org.apache.commons.logging.LogFactory;
035    
036    public class SnapshotDBHelper {
037        private static Log log = LogFactory.getLog(SnapshotDBHelper.class);
038        // field attributes for the Statistics table in the DB
039        private static final String SNAPSHOT_TIME = "snapshot_time";
040        private static final String MBEANNAME = "mbeanName";
041        private static final String STATSVALUELIST = "statsValueList";
042        private static final String STATSNAMELIST = "statsNameList";
043        // Connection object used for DB interaction
044        private static Connection conn = null;
045        // Data Sources
046        private static DataSource activeDS = null;
047        private static DataSource archiveDS = null;
048        
049        public SnapshotDBHelper() {
050            
051        }
052        
053        public SnapshotDBHelper(DataSource activeDS, DataSource archiveDS) {
054            SnapshotDBHelper.activeDS = activeDS;
055            SnapshotDBHelper.archiveDS = archiveDS;
056        }
057        
058        /**
059         * @return A map: mbeanName --> ArrayList of statistic attributes for that mbean
060         */
061        public HashMap<String, ArrayList<String>> getAllSnapshotStatAttributes() {
062            openActiveConnection();
063            HashMap<String, ArrayList<String>> retval = new HashMap<String, ArrayList<String>>();
064            try {
065                Statement stmt = conn.createStatement();
066                String query = "SELECT DISTINCT mbeanName, statsNameList FROM MBeans";
067                ResultSet rs = stmt.executeQuery(query);
068                // add each mbean/statsValue combination to retval
069                while(rs.next()) {
070                    String mbeanName = rs.getString(MBEANNAME);
071                    String statsNameStr = rs.getString(STATSNAMELIST);
072                    String[] statsNameList = statsNameStr.split(",");
073                    ArrayList<String> mbeanAttributeList = new ArrayList<String>();
074                    // copy from String[] to ArrayList<String>
075                    for(int i = 0; i < statsNameList.length; i++) {
076                        mbeanAttributeList.add(statsNameList[i]);
077                    }
078                    retval.put(mbeanName, mbeanAttributeList);
079                }
080            } catch(Exception e) {
081                log.error(e.getMessage(), e);
082            } finally {
083                closeConnection();
084            }
085            return retval;
086        }
087        
088        /**
089         * 
090         * @return The number of snapshots present in the active database
091         */
092        public Long getSnapshotCount() {
093            long retval = 0;
094            try {
095                openActiveConnection();
096                Statement stmt = conn.createStatement();
097                String query = "SELECT COUNT(DISTINCT snapshot_time) FROM Statistics";
098                ResultSet rs = stmt.executeQuery(query);
099                rs.next();
100                retval = rs.getLong(1);
101            } catch(Exception e) {
102                log.error(e.getMessage(), e);
103            } finally {
104                closeConnection();
105            }
106            return new Long(retval);
107        }
108        
109        /**
110         * @param numberOfSnapshots - the number of latest snapshots to look at
111         * @return A hashmap which maps an mbean --> a hashmap with an attribute name and its value . All values will be the max.
112         */
113        public HashMap<String, HashMap<String, Long>> fetchMaxSnapshotData(Integer numberOfSnapshots) {
114            return fetchMaxOrMinSnapshotData(numberOfSnapshots, true);
115        }
116        /**
117         * @param numberOfSnapshots - the number of latest snapshots to look at
118         * @return A hashmap which maps an mbean --> a hashmap with an attribute name and its value . All values will be the min.
119         */    
120        public HashMap<String, HashMap<String, Long>> fetchMinSnapshotData(Integer numberOfSnapshots) {
121            return fetchMaxOrMinSnapshotData(numberOfSnapshots, false);
122        }
123        
124        /**
125         * @param numberOfSnapshots - the number of latest snapshots to look at.
126         * @param isMax - true if the result should be all maximum values. otherwise, false.
127         * @return A hashmap which maps an mbean --> a hashmap with an attribute name and its value . All values will be the min
128         * or max, depending on the isMax parameter.
129         */
130        private HashMap<String, HashMap<String, Long>> fetchMaxOrMinSnapshotData(Integer numberOfSnapshots, boolean isMax) {
131            openActiveConnection();
132            ResultSet snapshotTimeTable = fetchSnapshotTimesFromDB();
133            HashMap<String, HashMap<String, Long>> stats = new HashMap<String, HashMap<String, Long>>();
134            try {
135                // for each snapshot in the table
136                while(snapshotTimeTable.next()) {
137                    Long snapshotTime = snapshotTimeTable.getLong(SNAPSHOT_TIME);
138                    // retrieve the snapshot information by time
139                    ResultSet snapshotData = fetchSnapshotDataFromDB(snapshotTime);
140                    // for each statistic, perform a relaxation
141                    while(snapshotData.next()) {
142                        String mbean = snapshotData.getString(MBEANNAME);
143                        // get map associated with mbean
144                        HashMap<String, Long> mbeanMap = stats.get(mbean);
145                        if(mbeanMap == null) {
146                            mbeanMap = new HashMap<String, Long>();
147                        }
148                        String[] statsNameList = snapshotData.getString(STATSNAMELIST).split(",");
149                        String[] statsValueList = snapshotData.getString(STATSVALUELIST).split(",");
150                        assert(statsNameList.length == statsValueList.length);
151                        // for each statname/statsvalue combo in an mbean
152                        for(int i = 0 ; i < statsNameList.length; i++) {
153                            String statsName = statsNameList[i];
154                            Long maxStatsValue = mbeanMap.get(statsName);
155                            // give maxStatsValue some value if there isn't one
156                            if(maxStatsValue == null) {
157                                if(isMax) {
158                                    maxStatsValue = new Long(0);
159                                } else {
160                                    maxStatsValue = Long.MAX_VALUE;
161                                }
162                            }
163                            // relax
164                            if(isMax) {
165                                maxStatsValue = new Long(Math.max(Long.parseLong(statsValueList[i]), maxStatsValue.longValue()));
166                            } else {
167                                maxStatsValue = new Long(Math.min(Long.parseLong(statsValueList[i]), maxStatsValue.longValue()));
168                            }
169                            // save name/value back into mbeanMap
170                            mbeanMap.put(statsName, maxStatsValue);
171                        }
172                        // save mbeanMap back into stats
173                        stats.put(mbean, mbeanMap);
174                    }
175                    
176                    // compute the remaining snapshots left to look at
177                    numberOfSnapshots--;
178                    // discontinue once we have looked at numberOfSnapshots snapshots
179                    if(numberOfSnapshots == 0) {
180                        break;
181                    }
182                }
183            } catch(Exception e) {
184                log.error(e.getMessage(), e);
185            } finally {
186                closeConnection();
187            }
188            return stats;
189        }
190        
191        /**
192         * @param ds
193         * @param aggregateStats
194         * @return Returns a boolean if the snapshot statistics were successfully added
195         * to the DB.
196         */
197        public boolean addSnapshotToDB(HashMap<String, HashMap<String, Long>> aggregateStats) {
198            boolean success = true;
199            // get the current time from 1970
200            String currTime = "";
201            currTime += (new Date()).getTime();
202            try {
203                // for each mbean
204                for(Iterator itt = aggregateStats.keySet().iterator(); itt.hasNext(); ) {
205                    String mbean = (String)itt.next();
206                    // prepare the statsNameList and statsValueList beforehand
207                    String statsNameList = "", statsValueList = "";
208                    for(Iterator<String> it = aggregateStats.get(mbean).keySet().iterator(); it.hasNext(); ) {
209                        String statsName = it.next();
210                        Long statsValue = aggregateStats.get(mbean).get(statsName);
211                        if(statsNameList.length() == 0 || statsValueList.length() == 0) {
212                            // do not add a comma because this is the first occurrence
213                            statsValueList += statsValue.toString();
214                            statsNameList += statsName;
215                        } else {
216                            // add a comma
217                            statsValueList += "," + statsValue.toString();
218                            statsNameList += "," + statsName;
219                        }
220                    }
221                    
222                    // start talking to DB
223                    openActiveConnection();
224                    Statement stmt = conn.createStatement();
225                    HashMap stats = aggregateStats.get(mbean);
226                    //--------Ensure MBeans are in place
227                    int mbeanId = getMBeanId(mbean); 
228                    if(mbeanId != -1) {
229                        // mbean already exists in the db
230                    } else {
231                        // doesn't exist in the db so add it
232                        // add mbean record to the db
233                        stmt.executeUpdate("INSERT INTO MBeans (mbeanName, statsNameList) VALUES ("+ surroundWithQuotes(mbean) + "," + surroundWithQuotes(statsNameList) + ")");
234                        mbeanId = getMBeanId(mbean);
235                    }
236                    
237                    // insert the statistics into Statistics table
238                    stmt.executeUpdate( prepareInsertSnapshotStatement(currTime, statsValueList, mbeanId) );
239                    closeConnection();
240                }
241            } catch(Exception  e){
242                log.error(e.getMessage(), e);
243                success = false;
244            } finally {
245                closeConnection();
246            }
247            
248            // go through the archiving process
249            try {
250                int retentionDays = Integer.parseInt(SnapshotConfigXMLBuilder.getAttributeValue("retention"));
251                long retentionMillis = (long)(retentionDays) * 86400000; // convert from days to milliseconds
252                archiveSnapshots( Long.parseLong(currTime) - retentionMillis );
253            } catch(Exception e) {
254                log.warn("Cannot archive snapshots because attribute 'retention' is not present in snapshot-config.xml.");
255            }
256            return success;
257        }
258        
259        /**
260         * Moves records from the ActiveDB to the ArchiveDB. The records that are moved
261         * are those whose snapshot_times exceed the retention period
262         * @param cutOffTime - in milliseconds
263         */
264        private void archiveSnapshots(long cutOffTime) {
265            // for each successful update of Snapshots/Statistics table
266            // increment or decrement these counters to ensure that nothing is being 
267            // lost in between. If these counters are non-zero, some records have been
268            // lost.
269            int snapshotsOver = 0;
270            int statisticsOver = 0;
271            try {
272                openActiveConnection();
273                ResultSet overDueSnapshotTimes = getOverDueSnapshotTimes(cutOffTime);
274                ArrayList<Long> overdueTimes = new ArrayList<Long>();
275                // save overdue times into an array list for later usage
276                while(overDueSnapshotTimes.next()) {
277                    overdueTimes.add(overDueSnapshotTimes.getLong(SNAPSHOT_TIME));
278                }
279                closeConnection();
280                // for each overdue snapshot time
281                // -transfer all records associated with that snaphot_time to ArchiveDB
282                for(int i = 0; i < overdueTimes.size(); i++) {
283                    long snapshotTime = overdueTimes.get(i);
284                    openActiveConnection();
285                    ResultSet rsSnapshotData = fetchSnapshotDataFromDB(new Long(snapshotTime));
286                    HashMap<String, HashMap<String, Long>> snapshotData = new HashMap<String,HashMap<String, Long>>();
287                    while(rsSnapshotData.next()) {
288                        // extract values from sql table
289                        String mbeanName = rsSnapshotData.getString(MBEANNAME);
290                        String statsNameList = rsSnapshotData.getString(STATSNAMELIST);
291                        String statsValueList = rsSnapshotData.getString(STATSVALUELIST);
292                        Long snapshot_time = rsSnapshotData.getLong(SNAPSHOT_TIME);
293                        // get a connection to the archive db too
294                        Connection archiveConn = archiveDS.getConnection();
295                        Statement archiveStmt = archiveConn.createStatement();
296                        //--------Ensure MBeans are in place
297                        int mbeanId = getMBeanIdFromArchive(mbeanName); 
298                        if(mbeanId != -1) {
299                            // mbean already exists in the db
300                        } else {
301                            // doesn't exist in the db so add it
302                            // add mbean record to the db
303                            archiveStmt.executeUpdate("INSERT INTO MBeans (mbeanName, statsNameList) VALUES ("+ surroundWithQuotes(mbeanName) + ", " + surroundWithQuotes(statsNameList) + ")");
304                            mbeanId = getMBeanIdFromArchive(mbeanName);
305                        }
306                        // ensure Statistics table has record of mbeanId, snapshotId, statsValue, statsName
307                        String updateStr = prepareInsertSnapshotStatement(snapshot_time + "", statsValueList, mbeanId);
308                        statisticsOver += archiveStmt.executeUpdate( updateStr );
309                        // close connection to archiveDB
310                        archiveConn.close();
311                    }
312                    closeConnection();
313                }
314                // for each snapshot time, remove all instances that is associated with in 
315                // in the active DB
316                for(int i = 0; i < overdueTimes.size(); i++) {
317                    long snapshotTime = overdueTimes.get(i);
318                    openActiveConnection();
319                    Statement stmt = conn.createStatement();
320                    // remove from Statistics table
321                    String statisticsUpdate = "DELETE FROM Statistics WHERE snapshot_time=" + snapshotTime;
322                    statisticsOver -= stmt.executeUpdate(statisticsUpdate);
323                    closeConnection();
324                }
325            } catch(Exception e) {
326                log.error(e.getMessage(), e);
327            } finally {
328                closeConnection();
329            }
330            
331            // ensure that the transferring was good
332            if(snapshotsOver != 0) {
333                log.warn("Transferred snapshots was completed, but some things were lost.");
334            }
335            if(statisticsOver != 0) {
336                log.warn("Transferred statistics was completed, but some things were lost.");
337            }
338        }
339        
340        /**
341         * @param cutOffTime
342         * @return An SQL table contain a column of all the times that did not make the cutOffTime.
343         */
344        private ResultSet getOverDueSnapshotTimes(long cutOffTime) {
345            try {
346                Statement stmt = conn.createStatement();
347                String query = "SELECT DISTINCT snapshot_time FROM Statistics WHERE snapshot_time < " + cutOffTime;
348                return stmt.executeQuery(query);
349            } catch(Exception e) {
350                log.error(e.getMessage(), e);
351            }
352            return null;
353        }
354        
355        /**
356         * @param mbean
357         * @return The mbean id of the mbean from table ArchiveDB.MBean. Returns -1 if record does not exist.
358         */
359        private int getMBeanIdFromArchive(String mbean) throws Exception {
360            int retval = -1;
361            Connection archiveConn = archiveDS.getConnection();
362            Statement stmt = archiveConn.createStatement();
363            ResultSet rs = stmt.executeQuery("SELECT id FROM MBeans WHERE mbeanName=" + surroundWithQuotes(mbean));
364            if(rs.next()) {
365                retval = rs.getInt("id");
366            }
367            stmt.close();
368            archiveConn.close();
369            return retval;
370        }
371        
372        /**
373         * @param mbean
374         * @return The mbean id of the mbean from table ActiveDB.MBean. Returns -1 if record does not exist.
375         */
376        private int getMBeanId(String mbean) throws Exception {
377            int retval = -1;
378            Connection conn = activeDS.getConnection();
379            Statement stmt = conn.createStatement();
380            ResultSet rs = stmt.executeQuery("SELECT id FROM MBeans WHERE mbeanName=" + surroundWithQuotes(mbean));
381            if(rs.next()) {
382                retval = rs.getInt("id");
383            }
384            stmt.close();
385            conn.close();
386            return retval;
387        }
388        
389        /**
390         * @param snapshot_time
391         * @param statsValueList
392         * @param mbeanId
393         * @return Returns an SQL insert statement for one statistic given the correct information.
394         */
395        public String prepareInsertSnapshotStatement(String snapshot_time, String statsValueList, int mbeanId) {
396            String retval = "INSERT INTO Statistics (snapshot_time, statsValueList, mbeanId) VALUES (";
397            retval += snapshot_time;
398            retval += ",";
399            retval += surroundWithQuotes(statsValueList);
400            retval += ",";
401            retval += mbeanId;
402            retval += ")";
403            return retval;
404        }
405    
406        /**
407         * @param s
408         * @return A String with ' at the beginning and end
409         */
410        private String surroundWithQuotes(String s) {
411            return "'" + s.trim() + "'";
412        }
413        
414        /**
415         * Fetches the data stored from the snapshot thread and returns
416         * it in a ArrayList with each element being a HashMap of the attribute
417         * mapping to the statistic. Grabs 'numberOfSnapshots' snapshots. Grabs 
418         * one snapshot per 'everyNthsnapshot'
419         * 
420         * @param numberOfSnapshot
421         * @param everyNthSnapshot
422         * @return ArrayList
423         */ 
424        public ArrayList<HashMap<String, HashMap<String, Object>>> fetchData(Integer numberOfSnapshot, 
425                                                                                    Integer everyNthSnapshot) {
426            ArrayList<HashMap<String, HashMap<String, Object>>> stats = new ArrayList<HashMap<String, HashMap<String, Object>>>();
427            openActiveConnection();
428            // get all records in the database grouped and ordered by time
429            ResultSet table = fetchSnapshotTimesFromDB();
430            // iterate through the table and finds the times (which uniquely IDs a snapshot)
431            // that are wanted and queries the rest of the DB using the time as the condition
432            // (i.e. the ones that are in the c*n-th snapshot where c <= numberOfSnapshot
433            // and n == everyNthSnapshot)
434            int nthSnapshot = 0;
435            try {
436                while(table.next()) {
437                    Long snapshotTime = table.getLong(SNAPSHOT_TIME);
438                    // grab 0*nth, 1*nth, 2*nth snapshot up to min(numberOfSnapshot*everyNthSnapshot, size of the table)
439                    if(nthSnapshot % everyNthSnapshot == 0) {
440                        HashMap<String, HashMap<String, Object>> snapshotData = packageSnapshotData(snapshotTime);
441                        stats.add( 0, snapshotData );
442                        numberOfSnapshot--;
443                    }
444                    nthSnapshot++;
445                    // no more snapshots needs to be looked at, we have successfully acquired our goal
446                    if(numberOfSnapshot == 0) {
447                        break;
448                    }
449                }
450            } catch(Exception e) {
451                log.error(e.getMessage(), e);
452            } finally {
453                closeConnection();
454            }
455            return stats;
456        }
457        
458        /**
459         * @param snapshotTime
460         * @return A hashmap in the form <String1, HashMap> where String1 is the mbean name
461         * and HashMap is a map containing a <String2, Object> where String2 is an attribute name
462         * and Object is the value. Additionally, if String is "times" then it maps to a HashMap
463         * containing snapshot_time and snapshot_date information.
464         */
465        private HashMap<String, HashMap<String, Object>> packageSnapshotData(Long snapshotTime) {
466            HashMap<String, HashMap<String, Object>> snapshotPkg = new HashMap<String, HashMap<String, Object>>();
467            openActiveConnection();
468            ResultSet snapshotData = fetchSnapshotDataFromDB(snapshotTime);
469            try {
470                // for each record save it somewhere in the snapshotPkg
471                while(snapshotData.next()) {
472                    String currMBean = snapshotData.getString(MBEANNAME);
473                    // get the information for the mbean defined by currMBean
474                    HashMap<String, Object> mbeanInfo = snapshotPkg.get(currMBean);
475                    if(mbeanInfo == null) {
476                        mbeanInfo = new HashMap<String, Object>();
477                    }
478                    // get statistics from resultset
479                    String statsValueStr = snapshotData.getString(STATSVALUELIST);
480                    String statsNameStr = snapshotData.getString(STATSNAMELIST);
481                    String[] statsValueList = statsValueStr.split(",");
482                    String[] statsNameList = statsNameStr.split(",");
483                    assert(statsValueList.length == statsNameList.length);
484                    // for each statsValue/statsName, save it
485                    for(int i = 0 ; i < statsValueList.length; i++) {
486                        long statValue = Long.parseLong(statsValueList[i]);
487                        mbeanInfo.put(statsNameList[i], new Long(statValue));
488                    }
489                    // save the hashmap into the snapshotpkg
490                    snapshotPkg.put(currMBean, mbeanInfo);
491                }
492            } catch(Exception e) {
493                log.error(e.getMessage(), e);
494            } finally {
495                closeConnection();
496            }
497            // add the time and date
498            HashMap<String, Object> timeMap = new HashMap<String, Object>();
499            timeMap.put(SNAPSHOT_TIME, snapshotTime);
500            snapshotPkg.put("times", timeMap);
501            
502            return snapshotPkg;
503        }
504        
505        /**
506         * @param snapshotTime
507         * @return Returns a ResultSet with all statistic information that matches the snapshot_time.
508         */
509        private ResultSet fetchSnapshotDataFromDB(Long snapshotTime) {
510            String query = "SELECT S.statsValueList AS statsValueList, M.statsNameList AS statsNameList, S.snapshot_time AS snapshot_time, M.mbeanName AS mbeanName FROM Statistics S, MBeans M WHERE S.snapshot_time=" + snapshotTime;
511            query += " AND S.mbeanId=M.id";
512            ResultSet retval = null;
513            try {
514                if(conn.isClosed()) {
515                    openActiveConnection();
516                }
517                Statement stmt = conn.createStatement();
518                retval = stmt.executeQuery(query);
519            } catch(Exception e) {
520                log.error(e.getMessage(), e);
521            }
522            return retval;
523        }
524    
525        /**
526         * @return Returns a ResultSet with one column (snapshot_time) sorted in descending order
527         */
528        private ResultSet fetchSnapshotTimesFromDB() {
529            String query = "SELECT DISTINCT snapshot_time FROM Statistics ORDER BY snapshot_time DESC";
530            ResultSet retval = null;
531            try {
532                if(conn.isClosed()) {
533                    openActiveConnection();
534                }
535                Statement stmt = conn.createStatement();
536                retval = stmt.executeQuery(query);
537            } catch(Exception e) {
538                log.error(e.getMessage(), e);
539            }
540            return retval;
541        }
542        
543        /**
544         * Opens the global connection to activeDB
545         */
546        private void openActiveConnection() {
547            try {
548                conn = activeDS.getConnection();
549            } catch(Exception e) {
550                log.error(e.getMessage(), e);
551            }
552        }
553        
554        /**
555         * Opens the global connection to archiveDB
556         */
557        private void openArchiveConnection() {
558            try {
559                conn = archiveDS.getConnection();
560            } catch(Exception e) {
561                log.error(e.getMessage(), e);
562            }
563        }
564        
565        /**
566         * Closes the global connection to a DB
567         */
568        private void closeConnection() {
569            if(conn != null) {
570                try {
571                    conn.close();
572                } catch(Exception e) {
573                    log.error(e.getMessage(), e);
574                }
575            }
576        }
577        
578        /**
579         * @param mbeanName
580         * @param statsName
581         * @param numberOfSnapshots
582         * @param everyNthSnapshot
583         * @return HashMap which maps from a snapshot_time --> value of the mbean.statsName at that time
584         */
585        public TreeMap<Long, Long> getSpecificStatistics(    String mbeanName, 
586                                                                    String statsName, 
587                                                                    int numberOfSnapshots, 
588                                                                    int everyNthSnapshot, 
589                                                                    boolean showArchived) {
590            openActiveConnection();
591            TreeMap<Long, Long> stats = new TreeMap<Long, Long>();
592            int nthSnapshot = 0;
593            // attempt to get as many snapshots from the active db as possible
594            try {
595                Statement stmt = conn.createStatement();
596                int mbeanId = getMBeanId(mbeanName);
597                if(mbeanId == -1) {
598                    log.error(mbeanName + " does not exist in the database.");
599                } else {
600                    String query = "SELECT DISTINCT snapshot_time, statsValueList, statsNameList FROM Statistics, MBeans M WHERE mbeanId=" + mbeanId + " AND mbeanId=M.id ORDER BY snapshot_time DESC";
601                    ResultSet rs = stmt.executeQuery(query);
602                    // iterate through the table paying attention to those at everyNthSnapshot-th position
603                    while(rs.next()) {
604                        // every nth snapshot I save the information into my returning hashmap
605                        if(nthSnapshot % everyNthSnapshot == 0) {
606                            String[] statsValueList = rs.getString(STATSVALUELIST).split(",");
607                            String[] statsNameList = rs.getString(STATSNAMELIST).split(",");
608                            assert(statsValueList.length == statsNameList.length);
609                            Long statsValue = null;
610                            for(int i = 0 ; i < statsNameList.length; i++) {
611                                if(statsNameList[i].equals(statsName)) {
612                                    long value = Long.parseLong(statsValueList[i]);
613                                    statsValue = new Long(value);
614                                }
615                            }
616                            // exit function after error
617                            if(statsValue == null) {
618                                log.warn("Statistics name '" + statsName + "' does not exist");
619                                return stats;
620                            } else {
621                                stats.put(rs.getLong(SNAPSHOT_TIME), statsValue);
622                                numberOfSnapshots--;
623                            }
624                        }
625                        // update counter
626                        nthSnapshot++;
627                        // enough data, end this thing
628                        if(numberOfSnapshots == 0) {
629                            break;
630                        }
631                    }
632                }
633            } catch(Exception e) {
634                log.error(e.getMessage(), e);
635            } finally {
636                closeConnection();
637            }
638    
639            nthSnapshot = 0;
640    
641            // attempt to get the remaining snapshots requested from the archive DB
642            // iff the showArchive flag is set
643            if(showArchived && numberOfSnapshots != 0) {
644                try {
645                    openArchiveConnection();    // connection to the Archive DB
646                    Statement stmt = conn.createStatement();
647                    int mbeanId = getMBeanId(mbeanName);
648                    if(mbeanId == -1) {
649                        log.error(mbeanName + " does not exist in the database.");
650                    } else {
651                        String query = "SELECT DISTINCT snapshot_time, statsValueList, statsNameList FROM Statistics, MBeans M WHERE mbeanId=" + mbeanId + " AND mbeanId=M.id ORDER BY snapshot_time DESC";
652                        ResultSet rs = stmt.executeQuery(query);
653                        // iterate through the table paying attention to those at everyNthSnapshot-th position
654                        while(rs.next()) {
655                            // every nth snapshot I save the information into my returning hashmap
656                            if(nthSnapshot % everyNthSnapshot == 0) {
657                                String[] statsValueList = rs.getString(STATSVALUELIST).split(",");
658                                String[] statsNameList = rs.getString(STATSNAMELIST).split(",");
659                                assert(statsValueList.length == statsNameList.length);
660                                Long statsValue = null;
661                                for(int i = 0 ; i < statsNameList.length; i++) {
662                                    if(statsNameList[i].equals(statsName)) {
663                                        long value = Long.parseLong(statsValueList[i]);
664                                        statsValue = new Long(value);
665                                    }
666                                }
667                                // exit function after error
668                                if(statsValue == null) {
669                                    log.warn("Statistics name '" + statsName + "' does not exist");
670                                    return stats;
671                                } else {
672                                    stats.put(rs.getLong(SNAPSHOT_TIME), statsValue);
673                                    numberOfSnapshots--;
674                                }
675                            }
676                            // update counter
677                            nthSnapshot++;
678                            // enough data, end this thing
679                            if(numberOfSnapshots == 0) {
680                                break;
681                            }
682                        }
683                    }
684                } catch(Exception e) {
685                    log.error(e.getMessage(), e);
686                } finally {
687                    closeConnection();
688                }
689            }
690            return stats;
691        }
692        
693        /**
694         * Sets the necessary data sources for this helper to talk to the db
695         * @param activeDS
696         * @param archiveDS
697         */
698        public void setDataSources(DataSource active, DataSource archive) {
699            activeDS = active;
700            archiveDS = archive;
701        }
702    }