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 */
017package org.apache.activemq.kaha.impl.async;
018
019import java.io.File;
020import java.io.IOException;
021import java.io.RandomAccessFile;
022import java.nio.channels.FileLock;
023import java.nio.channels.OverlappingFileLockException;
024
025import org.apache.activemq.util.ByteSequence;
026import org.apache.activemq.util.IOExceptionSupport;
027
028/**
029 * Use to reliably store fixed sized state data. It stores the state in record
030 * that is versioned and repeated twice in the file so that a failure in the
031 * middle of the write of the first or second record do not not result in an
032 * unknown state.
033 * 
034 * 
035 */
036public final class ControlFile {
037
038    private static final boolean DISABLE_FILE_LOCK = "true".equals(System.getProperty("java.nio.channels.FileLock.broken", "false"));
039    private final File file;
040
041    /** The File that holds the control data. */
042    private final RandomAccessFile randomAccessFile;
043    private final int maxRecordSize;
044    private final int firstRecordStart;
045    private final int secondRecordStart;
046    private final int firstRecordEnd;
047    private final int secondRecordEnd;
048
049    private long version;
050    private FileLock lock;
051    private boolean disposed;
052
053    public ControlFile(File file, int recordSize) throws IOException {
054        this.file = file;
055        this.maxRecordSize = recordSize + 4;
056        
057        // Calculate where the records start and end.
058        this.firstRecordStart = 8;
059        this.secondRecordStart = 8 + maxRecordSize + 8 + 8;
060        this.firstRecordEnd = firstRecordStart+maxRecordSize;
061        this.secondRecordEnd = secondRecordStart+maxRecordSize;
062
063        randomAccessFile = new RandomAccessFile(file, "rw");
064    }
065
066    /**
067     * Locks the control file.
068     * 
069     * @throws IOException
070     */
071    public void lock() throws IOException {
072        if (DISABLE_FILE_LOCK) {
073            return;
074        }
075
076        if (lock == null) {
077            try {
078                lock = randomAccessFile.getChannel().tryLock(0, Math.max(1, randomAccessFile.getChannel().size()), false);
079            } catch (OverlappingFileLockException e) {
080                throw IOExceptionSupport.create("Control file '" + file + "' could not be locked.",e);
081            }
082            if (lock == null) {
083                throw new IOException("Control file '" + file + "' could not be locked.");
084            }
085        }
086    }
087
088    /**
089     * Un locks the control file.
090     * 
091     * @throws IOException
092     */
093    public void unlock() throws IOException {
094        if (DISABLE_FILE_LOCK) {
095            return;
096        }
097
098        if (lock != null) {
099            lock.release();
100            lock = null;
101        }
102    }
103
104    public void dispose() {
105        if (disposed) {
106            return;
107        }
108        disposed = true;
109        try {
110            unlock();
111        } catch (IOException ignore) {
112        }
113        try {
114            randomAccessFile.close();
115        } catch (IOException ignore) {
116        }
117    }
118
119    public synchronized ByteSequence load() throws IOException {
120        long l = randomAccessFile.length();
121        if (l < maxRecordSize) {
122            return null;
123        }
124
125        randomAccessFile.seek(firstRecordStart-8);
126        long v1 = randomAccessFile.readLong();
127        randomAccessFile.seek(firstRecordEnd);
128        long v1check = randomAccessFile.readLong();
129
130        randomAccessFile.seek(secondRecordStart - 8);
131        long v2 = randomAccessFile.readLong();
132        randomAccessFile.seek(secondRecordEnd);
133        long v2check = randomAccessFile.readLong();
134
135        byte[] data = null;
136        if (v2 == v2check) {
137            version = v2;
138            randomAccessFile.seek(secondRecordStart);
139            int size = randomAccessFile.readInt();
140            data = new byte[size];
141            randomAccessFile.readFully(data);
142        } else if (v1 == v1check) {
143            version = v1;
144            randomAccessFile.seek(firstRecordStart);
145            int size = randomAccessFile.readInt();
146            data = new byte[size];
147            randomAccessFile.readFully(data);
148        } else {
149            // Bummer.. Both checks are screwed. we don't know
150            // if any of the two buffer are ok. This should
151            // only happen is data got corrupted.
152            throw new IOException("Control data corrupted.");
153        }
154        return new ByteSequence(data, 0, data.length);
155    }
156
157    public void store(ByteSequence data, boolean sync) throws IOException {
158
159        version++;
160        randomAccessFile.setLength((maxRecordSize * 2) + 32);
161        randomAccessFile.seek(0);
162
163        // Write the first copy of the control data.
164        randomAccessFile.writeLong(version);
165        randomAccessFile.writeInt(data.getLength());
166        randomAccessFile.write(data.getData());
167        randomAccessFile.seek(firstRecordEnd);
168        randomAccessFile.writeLong(version);
169
170        // Write the second copy of the control data.
171        randomAccessFile.writeLong(version);
172        randomAccessFile.writeInt(data.getLength());
173        randomAccessFile.write(data.getData());
174        randomAccessFile.seek(secondRecordEnd);
175        randomAccessFile.writeLong(version);
176
177        if (sync) {
178            randomAccessFile.getFD().sync();
179        }
180    }
181
182        public boolean isDisposed() {
183                return disposed;
184        }
185
186}