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 sun.nio.ch.DirectBuffer;
029    
030    import java.io.IOException;
031    import java.nio.MappedByteBuffer;
032    import java.nio.channels.FileChannel;
033    import java.util.Timer;
034    import java.util.TimerTask;
035    
036    /**
037     * JRobin backend which is used to store RRD data to ordinary disk files
038     * by using fast java.nio.* package. This is the default backend engine since JRobin 1.4.0.
039     */
040    public class RrdNioBackend extends RrdFileBackend {
041            private static final Timer fileSyncTimer = new Timer(true);
042    
043            private MappedByteBuffer byteBuffer;
044            private TimerTask syncTask = new TimerTask() {
045                    public void run() {
046                            sync();
047                    }
048            };
049    
050            /**
051             * Creates RrdFileBackend object for the given file path, backed by java.nio.* classes.
052             *
053             * @param path     Path to a file
054             * @param readOnly   True, if file should be open in a read-only mode. False otherwise
055             * @param syncPeriod See {@link RrdNioBackendFactory#setSyncPeriod(int)} for explanation
056             * @throws IOException Thrown in case of I/O error
057             */
058            protected RrdNioBackend(String path, boolean readOnly, int syncPeriod)
059                            throws IOException {
060                    super(path, readOnly);
061                    try {
062                            mapFile();
063                            if (!readOnly) {
064                                    fileSyncTimer.schedule(syncTask, syncPeriod * 1000L, syncPeriod * 1000L);
065                            }
066                    }
067                    catch (IOException ioe) {
068                            super.close();
069                            throw ioe;
070                    }
071            }
072    
073            private void mapFile() throws IOException {
074                    long length = getLength();
075                    if (length > 0) {
076                            FileChannel.MapMode mapMode =
077                                            readOnly ? FileChannel.MapMode.READ_ONLY : FileChannel.MapMode.READ_WRITE;
078                            byteBuffer = file.getChannel().map(mapMode, 0, length);
079                    }
080            }
081    
082            private void unmapFile() {
083                    if (byteBuffer != null) {
084                            if (byteBuffer instanceof DirectBuffer) {
085                                    ((DirectBuffer) byteBuffer).cleaner().clean();
086                            }
087                            byteBuffer = null;
088                    }
089            }
090    
091            /**
092             * Sets length of the underlying RRD file. This method is called only once, immediately
093             * after a new RRD file gets created.
094             *
095             * @param newLength Length of the RRD file
096             * @throws IOException Thrown in case of I/O error.
097             */
098            protected synchronized void setLength(long newLength) throws IOException {
099                    unmapFile();
100                    super.setLength(newLength);
101                    mapFile();
102            }
103    
104            /**
105             * Writes bytes to the underlying RRD file on the disk
106             *
107             * @param offset Starting file offset
108             * @param b       Bytes to be written.
109             */
110            protected synchronized void write(long offset, byte[] b) throws IOException {
111                    if (byteBuffer != null) {
112                            byteBuffer.position((int) offset);
113                            byteBuffer.put(b);
114                    }
115                    else {
116                            throw new IOException("Write failed, file " + getPath() + " not mapped for I/O");
117                    }
118            }
119    
120            /**
121             * Reads a number of bytes from the RRD file on the disk
122             *
123             * @param offset Starting file offset
124             * @param b       Buffer which receives bytes read from the file.
125             */
126            protected synchronized void read(long offset, byte[] b) throws IOException {
127                    if (byteBuffer != null) {
128                            byteBuffer.position((int) offset);
129                            byteBuffer.get(b);
130                    }
131                    else {
132                            throw new IOException("Read failed, file " + getPath() + " not mapped for I/O");
133                    }
134            }
135    
136            /**
137             * Closes the underlying RRD file.
138             *
139             * @throws IOException Thrown in case of I/O error
140             */
141            public synchronized void close() throws IOException {
142                    // cancel synchronization
143                    try {
144                            if (syncTask != null) {
145                                    syncTask.cancel();
146                            }
147                            sync();
148                            unmapFile();
149                    }
150                    finally {
151                            super.close();
152                    }
153            }
154    
155            /**
156             * This method forces all data cached in memory but not yet stored in the file,
157             * to be stored in it.
158             */
159            protected synchronized void sync() {
160                    if (byteBuffer != null) {
161                            byteBuffer.force();
162                    }
163            }
164    }