001    /* ============================================================
002     * JRobin : Pure java implementation of RRDTool's functionality
003     * ============================================================
004     *
005     * Project Info:  http://www.jrobin.org
006     * Project Lead:  Sasa Markovic (saxon@jrobin.org);
007     *
008     * (C) Copyright 2003-2005, by Sasa Markovic.
009     *
010     * Developers:    Sasa Markovic (saxon@jrobin.org)
011     *
012     *
013     * This library is free software; you can redistribute it and/or modify it under the terms
014     * of the GNU Lesser General Public License as published by the Free Software Foundation;
015     * either version 2.1 of the License, or (at your option) any later version.
016     *
017     * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
018     * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
019     * See the GNU Lesser General Public License for more details.
020     *
021     * You should have received a copy of the GNU Lesser General Public License along with this
022     * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
023     * Boston, MA 02111-1307, USA.
024     */
025    
026    package org.jrobin.core;
027    
028    import java.io.File;
029    import java.io.IOException;
030    import java.util.List;
031    import java.util.LinkedList;
032    import java.util.Arrays;
033    
034    /**
035     * <p>Class used to perform various complex operations on RRD files. Use an instance of the
036     * RrdToolkit class to:</p>
037     * <ul>
038     * <li>add datasource to a RRD file.
039     * <li>add archive to a RRD file.
040     * <li>remove datasource from a RRD file.
041     * <li>remove archive from a RRD file.
042     * </ul>
043     * <p>All these operations can be performed on the copy of the original RRD file, or on the
044     * original file itself (with possible backup file creation)</p>
045     * <p/>
046     * <p><b><u>IMPORTANT</u></b>: NEVER use methods found in this class on 'live' RRD files
047     * (files which are currently in use).</p>
048     */
049    public class RrdToolkit {
050            /**
051             * Creates a new RRD file with one more datasource in it. RRD file is created based on the
052             * existing one (the original RRD file is not modified at all). All data from
053             * the original RRD file is copied to the new one.
054             *
055             * @param sourcePath    path to a RRD file to import data from (will not be modified)
056             * @param destPath        path to a new RRD file (will be created)
057             * @param newDatasource Datasource definition to be added to the new RRD file
058             * @throws IOException  Thrown in case of I/O error
059             * @throws RrdException Thrown in case of JRobin specific error
060             */
061            public static void addDatasource(String sourcePath, String destPath, DsDef newDatasource)
062                            throws IOException, RrdException {
063                    if (Util.sameFilePath(sourcePath, destPath)) {
064                            throw new RrdException("Source and destination paths are the same");
065                    }
066                    RrdDb rrdSource = new RrdDb(sourcePath);
067                    try {
068                            RrdDef rrdDef = rrdSource.getRrdDef();
069                            rrdDef.setPath(destPath);
070                            rrdDef.addDatasource(newDatasource);
071                            RrdDb rrdDest = new RrdDb(rrdDef);
072                            try {
073                                    rrdSource.copyStateTo(rrdDest);
074                            }
075                            finally {
076                                    rrdDest.close();
077                            }
078                    }
079                    finally {
080                            rrdSource.close();
081                    }
082            }
083    
084            /**
085             * <p>Adds one more datasource to a RRD file.</p>
086             * <p>WARNING: This method is potentialy dangerous! It will modify your RRD file.
087             * It is highly recommended to preserve the original RRD file (<i>saveBackup</i>
088             * should be set to <code>true</code>). The backup file will be created in the same
089             * directory as the original one with <code>.bak</code> extension added to the
090             * original name.</p>
091             * <p>Before applying this method, be sure that the specified RRD file is not in use
092             * (not open)</p>
093             *
094             * @param sourcePath    path to a RRD file to add datasource to.
095             * @param newDatasource Datasource definition to be added to the RRD file
096             * @param saveBackup    true, if backup of the original file should be created;
097             *                      false, otherwise
098             * @throws IOException  Thrown in case of I/O error
099             * @throws RrdException Thrown in case of JRobin specific error
100             */
101            public static void addDatasource(String sourcePath, DsDef newDatasource, boolean saveBackup)
102                            throws IOException, RrdException {
103                    String destPath = Util.getTmpFilename();
104                    addDatasource(sourcePath, destPath, newDatasource);
105                    copyFile(destPath, sourcePath, saveBackup);
106            }
107    
108            /**
109             * Creates a new RRD file with one datasource removed. RRD file is created based on the
110             * existing one (the original RRD file is not modified at all). All remaining data from
111             * the original RRD file is copied to the new one.
112             *
113             * @param sourcePath path to a RRD file to import data from (will not be modified)
114             * @param destPath   path to a new RRD file (will be created)
115             * @param dsName         Name of the Datasource to be removed from the new RRD file
116             * @throws IOException  Thrown in case of I/O error
117             * @throws RrdException Thrown in case of JRobin specific error
118             */
119            public static void removeDatasource(String sourcePath, String destPath, String dsName)
120                            throws IOException, RrdException {
121                    if (Util.sameFilePath(sourcePath, destPath)) {
122                            throw new RrdException("Source and destination paths are the same");
123                    }
124                    RrdDb rrdSource = new RrdDb(sourcePath);
125                    try {
126                            RrdDef rrdDef = rrdSource.getRrdDef();
127                            rrdDef.setPath(destPath);
128                            rrdDef.removeDatasource(dsName);
129                            RrdDb rrdDest = new RrdDb(rrdDef);
130                            try {
131                                    rrdSource.copyStateTo(rrdDest);
132                            }
133                            finally {
134                                    rrdDest.close();
135                            }
136                    }
137                    finally {
138                            rrdSource.close();
139                    }
140            }
141    
142            /**
143             * <p>Removes single datasource from a RRD file.</p>
144             * <p>WARNING: This method is potentialy dangerous! It will modify your RRD file.
145             * It is highly recommended to preserve the original RRD file (<i>saveBackup</i>
146             * should be set to <code>true</code>). The backup file will be created in the same
147             * directory as the original one with <code>.bak</code> extension added to the
148             * original name.</p>
149             * <p>Before applying this method, be sure that the specified RRD file is not in use
150             * (not open)</p>
151             *
152             * @param sourcePath path to a RRD file to remove datasource from.
153             * @param dsName         Name of the Datasource to be removed from the RRD file
154             * @param saveBackup true, if backup of the original file should be created;
155             *                   false, otherwise
156             * @throws IOException  Thrown in case of I/O error
157             * @throws RrdException Thrown in case of JRobin specific error
158             */
159            public static void removeDatasource(String sourcePath, String dsName, boolean saveBackup)
160                            throws IOException, RrdException {
161                    String destPath = Util.getTmpFilename();
162                    removeDatasource(sourcePath, destPath, dsName);
163                    copyFile(destPath, sourcePath, saveBackup);
164            }
165    
166            /**
167             * Renames single datasource in the given RRD file.
168             *
169             * @param sourcePath Path to a RRD file
170             * @param oldDsName  Old datasource name
171             * @param newDsName  New datasource name
172             * @throws IOException  Thrown in case of I/O error
173             * @throws RrdException Thrown in case of JRobin specific error (invalid path or datasource names,
174             *                      for example)
175             */
176            public static void renameDatasource(String sourcePath, String oldDsName, String newDsName)
177                            throws IOException, RrdException {
178                    RrdDb rrd = new RrdDb(sourcePath);
179                    try {
180                            if (rrd.containsDs(oldDsName)) {
181                                    Datasource datasource = rrd.getDatasource(oldDsName);
182                                    datasource.setDsName(newDsName);
183                            }
184                            else {
185                                    throw new RrdException("Could not find datasource [" + oldDsName + "] in file " + sourcePath);
186                            }
187                    }
188                    finally {
189                            rrd.close();
190                    }
191            }
192    
193            /**
194             * Updates single or all datasource names in the specified RRD file
195             * by appending '!' (if not already present). Datasources with names ending with '!'
196             * will never store NaNs in RRA archives (zero value will be used instead). Might be useful
197             * from time to time
198             *
199             * @param sourcePath Path to a RRD file
200             * @param dsName         Datasource name or null if you want to rename all datasources
201             * @return Number of datasources successfully renamed
202             * @throws IOException  Thrown in case of I/O error
203             * @throws RrdException Thrown in case of JRobin specific error (invalid path or datasource name,
204             *                      for example)
205             */
206            public static int forceZerosForNans(String sourcePath, String dsName) throws IOException, RrdException {
207                    RrdDb rrd = new RrdDb(sourcePath);
208                    try {
209                            Datasource[] datasources;
210                            if (dsName == null) {
211                                    datasources = rrd.getDatasources();
212                            }
213                            else {
214                                    if (rrd.containsDs(dsName)) {
215                                            datasources = new Datasource[] {rrd.getDatasource(dsName)};
216                                    }
217                                    else {
218                                            throw new RrdException("Could not find datasource [" + dsName + "] in file " + sourcePath);
219                                    }
220                            }
221                            int count = 0;
222                            for (Datasource datasource : datasources) {
223                                    String currentDsName = datasource.getDsName();
224                                    if (!currentDsName.endsWith(DsDef.FORCE_ZEROS_FOR_NANS_SUFFIX)) {
225                                            datasource.setDsName(currentDsName + DsDef.FORCE_ZEROS_FOR_NANS_SUFFIX);
226                                            count++;
227                                    }
228                            }
229                            return count;
230                    }
231                    finally {
232                            rrd.close();
233                    }
234            }
235    
236            /**
237             * Creates a new RRD file with one more archive in it. RRD file is created based on the
238             * existing one (the original RRD file is not modified at all). All data from
239             * the original RRD file is copied to the new one.
240             *
241             * @param sourcePath path to a RRD file to import data from (will not be modified)
242             * @param destPath   path to a new RRD file (will be created)
243             * @param newArchive Archive definition to be added to the new RRD file
244             * @throws IOException  Thrown in case of I/O error
245             * @throws RrdException Thrown in case of JRobin specific error
246             */
247            public static void addArchive(String sourcePath, String destPath, ArcDef newArchive)
248                            throws IOException, RrdException {
249                    if (Util.sameFilePath(sourcePath, destPath)) {
250                            throw new RrdException("Source and destination paths are the same");
251                    }
252                    RrdDb rrdSource = new RrdDb(sourcePath);
253                    try {
254                            RrdDef rrdDef = rrdSource.getRrdDef();
255                            rrdDef.setPath(destPath);
256                            rrdDef.addArchive(newArchive);
257                            RrdDb rrdDest = new RrdDb(rrdDef);
258                            try {
259                                    rrdSource.copyStateTo(rrdDest);
260                            }
261                            finally {
262                                    rrdDest.close();
263                            }
264                    }
265                    finally {
266                            rrdSource.close();
267                    }
268            }
269    
270            /**
271             * <p>Adds one more archive to a RRD file.</p>
272             * <p>WARNING: This method is potentialy dangerous! It will modify your RRD file.
273             * It is highly recommended to preserve the original RRD file (<i>saveBackup</i>
274             * should be set to <code>true</code>). The backup file will be created in the same
275             * directory as the original one with <code>.bak</code> extension added to the
276             * original name.</p>
277             * <p>Before applying this method, be sure that the specified RRD file is not in use
278             * (not open)</p>
279             *
280             * @param sourcePath path to a RRD file to add datasource to.
281             * @param newArchive Archive definition to be added to the RRD file
282             * @param saveBackup true, if backup of the original file should be created;
283             *                   false, otherwise
284             * @throws IOException  Thrown in case of I/O error
285             * @throws RrdException Thrown in case of JRobin specific error
286             */
287            public static void addArchive(String sourcePath, ArcDef newArchive, boolean saveBackup)
288                            throws IOException, RrdException {
289                    String destPath = Util.getTmpFilename();
290                    addArchive(sourcePath, destPath, newArchive);
291                    copyFile(destPath, sourcePath, saveBackup);
292            }
293    
294            /**
295             * Creates a new RRD file with one archive removed. RRD file is created based on the
296             * existing one (the original RRD file is not modified at all). All relevant data from
297             * the original RRD file is copied to the new one.
298             *
299             * @param sourcePath path to a RRD file to import data from (will not be modified)
300             * @param destPath   path to a new RRD file (will be created)
301             * @param consolFun  Consolidation function of Archive which should be removed
302             * @param steps   Number of steps for Archive which should be removed
303             * @throws IOException  Thrown in case of I/O error
304             * @throws RrdException Thrown in case of JRobin specific error
305             */
306            public static void removeArchive(String sourcePath, String destPath, String consolFun, int steps)
307                            throws IOException, RrdException {
308                    if (Util.sameFilePath(sourcePath, destPath)) {
309                            throw new RrdException("Source and destination paths are the same");
310                    }
311                    RrdDb rrdSource = new RrdDb(sourcePath);
312                    try {
313                            RrdDef rrdDef = rrdSource.getRrdDef();
314                            rrdDef.setPath(destPath);
315                            rrdDef.removeArchive(consolFun, steps);
316                            RrdDb rrdDest = new RrdDb(rrdDef);
317                            try {
318                                    rrdSource.copyStateTo(rrdDest);
319                            }
320                            finally {
321                                    rrdDest.close();
322                            }
323                    }
324                    finally {
325                            rrdSource.close();
326                    }
327            }
328    
329            /**
330             * <p>Removes one archive from a RRD file.</p>
331             * <p>WARNING: This method is potentialy dangerous! It will modify your RRD file.
332             * It is highly recommended to preserve the original RRD file (<i>saveBackup</i>
333             * should be set to <code>true</code>). The backup file will be created in the same
334             * directory as the original one with <code>.bak</code> extension added to the
335             * original name.</p>
336             * <p>Before applying this method, be sure that the specified RRD file is not in use
337             * (not open)</p>
338             *
339             * @param sourcePath path to a RRD file to add datasource to.
340             * @param consolFun  Consolidation function of Archive which should be removed
341             * @param steps   Number of steps for Archive which should be removed
342             * @param saveBackup true, if backup of the original file should be created;
343             *                   false, otherwise
344             * @throws IOException  Thrown in case of I/O error
345             * @throws RrdException Thrown in case of JRobin specific error
346             */
347            public static void removeArchive(String sourcePath, String consolFun, int steps,
348                                                                             boolean saveBackup) throws IOException, RrdException {
349                    String destPath = Util.getTmpFilename();
350                    removeArchive(sourcePath, destPath, consolFun, steps);
351                    copyFile(destPath, sourcePath, saveBackup);
352            }
353    
354            private static void copyFile(String sourcePath, String destPath, boolean saveBackup)
355                            throws IOException {
356                    File source = new File(sourcePath);
357                    File dest = new File(destPath);
358                    if (saveBackup) {
359                            String backupPath = getBackupPath(destPath);
360                            File backup = new File(backupPath);
361                            deleteFile(backup);
362                            if (!dest.renameTo(backup)) {
363                                    throw new IOException("Could not create backup file " + backupPath);
364                            }
365                    }
366                    deleteFile(dest);
367                    if (!source.renameTo(dest)) {
368                            throw new IOException("Could not create file " + destPath + " from " + sourcePath);
369                    }
370            }
371    
372            private static String getBackupPath(String destPath) {
373                    String backupPath = destPath;
374                    do {
375                            backupPath += ".bak";
376                    } while (Util.fileExists(backupPath));
377                    return backupPath;
378            }
379    
380            /**
381             * Sets datasource heartbeat to a new value.
382             *
383             * @param sourcePath     Path to exisiting RRD file (will be updated)
384             * @param datasourceName Name of the datasource in the specified RRD file
385             * @param newHeartbeat   New datasource heartbeat
386             * @throws RrdException Thrown in case of JRobin specific error
387             * @throws IOException  Thrown in case of I/O error
388             */
389            public static void setDsHeartbeat(String sourcePath, String datasourceName,
390                                                                              long newHeartbeat) throws RrdException, IOException {
391                    RrdDb rrd = new RrdDb(sourcePath);
392                    try {
393                            Datasource ds = rrd.getDatasource(datasourceName);
394                            ds.setHeartbeat(newHeartbeat);
395                    }
396                    finally {
397                            rrd.close();
398                    }
399            }
400    
401            /**
402             * Sets datasource heartbeat to a new value.
403             *
404             * @param sourcePath   Path to exisiting RRD file (will be updated)
405             * @param dsIndex         Index of the datasource in the specified RRD file
406             * @param newHeartbeat New datasource heartbeat
407             * @throws RrdException Thrown in case of JRobin specific error
408             * @throws IOException  Thrown in case of I/O error
409             */
410            public static void setDsHeartbeat(String sourcePath, int dsIndex, long newHeartbeat)
411                            throws RrdException, IOException {
412                    RrdDb rrd = new RrdDb(sourcePath);
413                    try {
414                            Datasource ds = rrd.getDatasource(dsIndex);
415                            ds.setHeartbeat(newHeartbeat);
416                    }
417                    finally {
418                            rrd.close();
419                    }
420            }
421    
422            /**
423             * Sets datasource min value to a new value
424             *
425             * @param sourcePath               Path to exisiting RRD file (will be updated)
426             * @param datasourceName           Name of the datasource in the specified RRD file
427             * @param newMinValue             New min value for the datasource
428             * @param filterArchivedValues set to <code>true</code> if archived values less than
429             *                             <code>newMinValue</code> should be set to NaN; set to false, otherwise.
430             * @throws RrdException Thrown in case of JRobin specific error
431             * @throws IOException  Thrown in case of I/O error
432             */
433            public static void setDsMinValue(String sourcePath, String datasourceName,
434                                                                             double newMinValue, boolean filterArchivedValues) throws RrdException, IOException {
435                    RrdDb rrd = new RrdDb(sourcePath);
436                    try {
437                            Datasource ds = rrd.getDatasource(datasourceName);
438                            ds.setMinValue(newMinValue, filterArchivedValues);
439                    }
440                    finally {
441                            rrd.close();
442                    }
443            }
444    
445            /**
446             * Sets datasource max value to a new value.
447             *
448             * @param sourcePath               Path to exisiting RRD file (will be updated)
449             * @param datasourceName           Name of the datasource in the specified RRD file
450             * @param newMaxValue             New max value for the datasource
451             * @param filterArchivedValues set to <code>true</code> if archived values greater than
452             *                             <code>newMaxValue</code> should be set to NaN; set to false, otherwise.
453             * @throws RrdException Thrown in case of JRobin specific error
454             * @throws IOException  Thrown in case of I/O error
455             */
456            public static void setDsMaxValue(String sourcePath, String datasourceName,
457                                                                             double newMaxValue, boolean filterArchivedValues) throws RrdException, IOException {
458                    RrdDb rrd = new RrdDb(sourcePath);
459                    try {
460                            Datasource ds = rrd.getDatasource(datasourceName);
461                            ds.setMaxValue(newMaxValue, filterArchivedValues);
462                    }
463                    finally {
464                            rrd.close();
465                    }
466            }
467    
468            /**
469             * Updates valid value range for the given datasource.
470             *
471             * @param sourcePath               Path to exisiting RRD file (will be updated)
472             * @param datasourceName           Name of the datasource in the specified RRD file
473             * @param newMinValue             New min value for the datasource
474             * @param newMaxValue             New max value for the datasource
475             * @param filterArchivedValues set to <code>true</code> if archived values outside
476             *                             of the specified min/max range should be replaced with NaNs.
477             * @throws RrdException Thrown in case of JRobin specific error
478             * @throws IOException  Thrown in case of I/O error
479             */
480            public static void setDsMinMaxValue(String sourcePath, String datasourceName,
481                                                                                    double newMinValue, double newMaxValue, boolean filterArchivedValues)
482                            throws RrdException, IOException {
483                    RrdDb rrd = new RrdDb(sourcePath);
484                    try {
485                            Datasource ds = rrd.getDatasource(datasourceName);
486                            ds.setMinMaxValue(newMinValue, newMaxValue, filterArchivedValues);
487                    }
488                    finally {
489                            rrd.close();
490                    }
491            }
492    
493            /**
494             * Sets single archive's X-files factor to a new value.
495             *
496             * @param sourcePath Path to existing RRD file (will be updated)
497             * @param consolFun  Consolidation function of the target archive
498             * @param steps   Number of sptes of the target archive
499             * @param newXff         New X-files factor for the target archive
500             * @throws RrdException Thrown in case of JRobin specific error
501             * @throws IOException  Thrown in case of I/O error
502             */
503            public static void setArcXff(String sourcePath, String consolFun, int steps,
504                                                                     double newXff) throws RrdException, IOException {
505                    RrdDb rrd = new RrdDb(sourcePath);
506                    try {
507                            Archive arc = rrd.getArchive(consolFun, steps);
508                            arc.setXff(newXff);
509                    }
510                    finally {
511                            rrd.close();
512                    }
513            }
514    
515            /**
516             * Creates new RRD file based on the existing one, but with a different
517             * size (number of rows) for a single archive. The archive to be resized
518             * is identified by its consolidation function and the number of steps.
519             *
520             * @param sourcePath Path to the source RRD file (will not be modified)
521             * @param destPath   Path to the new RRD file (will be created)
522             * @param consolFun  Consolidation function of the archive to be resized
523             * @param numSteps   Number of steps of the archive to be resized
524             * @param newRows       New archive size (number of archive rows)
525             * @throws IOException  Thrown in case of I/O error
526             * @throws RrdException Thrown in case of JRobin specific error
527             */
528            public static void resizeArchive(String sourcePath, String destPath, String consolFun,
529                                                                             int numSteps, int newRows)
530                            throws IOException, RrdException {
531                    if (Util.sameFilePath(sourcePath, destPath)) {
532                            throw new RrdException("Source and destination paths are the same");
533                    }
534                    if (newRows < 2) {
535                            throw new RrdException("New arcihve size must be at least 2");
536                    }
537                    RrdDb rrdSource = new RrdDb(sourcePath);
538                    try {
539                            RrdDef rrdDef = rrdSource.getRrdDef();
540                            ArcDef arcDef = rrdDef.findArchive(consolFun, numSteps);
541                            if (arcDef.getRows() != newRows) {
542                                    arcDef.setRows(newRows);
543                                    rrdDef.setPath(destPath);
544                                    RrdDb rrdDest = new RrdDb(rrdDef);
545                                    try {
546                                            rrdSource.copyStateTo(rrdDest);
547                                    }
548                                    finally {
549                                            rrdDest.close();
550                                    }
551                            }
552                    }
553                    finally {
554                            rrdSource.close();
555                    }
556            }
557    
558            /**
559             * Modifies existing RRD file, by resizing its chosen archive. The archive to be resized
560             * is identified by its consolidation function and the number of steps.
561             *
562             * @param sourcePath Path to the RRD file (will be modified)
563             * @param consolFun  Consolidation function of the archive to be resized
564             * @param numSteps   Number of steps of the archive to be resized
565             * @param newRows       New archive size (number of archive rows)
566             * @param saveBackup true, if backup of the original file should be created;
567             *                   false, otherwise
568             * @throws IOException  Thrown in case of I/O error
569             * @throws RrdException Thrown in case of JRobin specific error
570             */
571            public static void resizeArchive(String sourcePath, String consolFun,
572                                                                             int numSteps, int newRows, boolean saveBackup)
573                            throws IOException, RrdException {
574                    String destPath = Util.getTmpFilename();
575                    resizeArchive(sourcePath, destPath, consolFun, numSteps, newRows);
576                    copyFile(destPath, sourcePath, saveBackup);
577            }
578    
579            private static void deleteFile(File file) throws IOException {
580                    if (file.exists() && !file.delete()) {
581                            throw new IOException("Could not delete file: " + file.getCanonicalPath());
582                    }
583            }
584    
585            /**
586             * Splits single RRD file with several datasources into a number of smaller RRD files
587             * with a single datasource in it. All archived values are preserved. If
588             * you have a RRD file named 'traffic.rrd' with two datasources, 'in' and 'out', this
589             * method will create two files (with a single datasource, in the same directory)
590             * named 'in-traffic.rrd' and 'out-traffic.rrd'.
591             *
592             * @param sourcePath Path to a RRD file with multiple datasources defined
593             * @throws IOException  Thrown in case of I/O error
594             * @throws RrdException Thrown in case of JRobin specific error
595             */
596            public static void split(String sourcePath) throws IOException, RrdException {
597                    RrdDb rrdSource = new RrdDb(sourcePath);
598                    try {
599                            String[] dsNames = rrdSource.getDsNames();
600                            for (String dsName : dsNames) {
601                                    RrdDef rrdDef = rrdSource.getRrdDef();
602                                    rrdDef.setPath(createSplitPath(dsName, sourcePath));
603                                    rrdDef.saveSingleDatasource(dsName);
604                                    RrdDb rrdDest = new RrdDb(rrdDef);
605                                    try {
606                                            rrdSource.copyStateTo(rrdDest);
607                                    }
608                                    finally {
609                                            rrdDest.close();
610                                    }
611                            }
612                    }
613                    finally {
614                            rrdSource.close();
615                    }
616            }
617    
618            /**
619             * Returns list of canonical file names with the specified extension in the given directory. This
620             * method is not RRD related, but might come handy to create a quick list of all RRD files
621             * in the given directory.
622             *
623             * @param directory Source directory
624             * @param extension File extension (like ".rrd", ".jrb", ".rrd.jrb")
625             * @param resursive true if all subdirectories should be traversed for the same extension, false otherwise
626             * @return Array of sorted canonical file names with the given extension
627             * @throws IOException Thrown in case of I/O error
628             */
629            public static String[] getCanonicalPaths(String directory, final String extension, boolean resursive)
630                            throws IOException {
631                    File baseDir = new File(directory);
632                    if (!baseDir.isDirectory()) {
633                            throw new IOException("Not a directory: " + directory);
634                    }
635                    List<String> fileList = new LinkedList<String>();
636                    traverseDirectory(new File(directory), extension, resursive, fileList);
637                    String[] result = fileList.toArray(new String[fileList.size()]);
638                    Arrays.sort(result);
639                    return result;
640            }
641    
642            private static void traverseDirectory(File directory, String extension, boolean recursive, List<String> list)
643                            throws IOException {
644                    File[] files = directory.listFiles();
645                    for (File file : files) {
646                            if (file.isDirectory() && recursive) {
647                                    // traverse subdirectories only if recursive flag is specified
648                                    traverseDirectory(file, extension, recursive, list);
649                            }
650                            else if (file.isFile() && file.getName().endsWith(extension)) {
651                                    list.add(file.getCanonicalPath());
652                            }
653                    }
654            }
655    
656            private static String createSplitPath(String dsName, String sourcePath) {
657                    File file = new File(sourcePath);
658                    String newName = dsName + "-" + file.getName();
659                    String path = file.getAbsolutePath();
660                    String parentDir = path.substring(0, 1 + path.lastIndexOf(Util.getFileSeparator()));
661                    return parentDir + newName;
662            }
663    }
664