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, 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}