001/* 002 * Copyright 2015 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.ametys.runtime.data; 018 019import java.io.File; 020import java.io.IOException; 021import java.io.RandomAccessFile; 022import java.nio.channels.FileChannel; 023import java.nio.channels.FileLock; 024import java.nio.channels.OverlappingFileLockException; 025 026/** 027 * Exclusive lock on the Ametys Home directory. 028 * Based on the Jackrabbit repository lock. 029 */ 030public class AmetysHomeLock 031{ 032 /** 033 * Name of the lock file within a directory. 034 */ 035 private static final String __LOCK_FILE = ".lock"; 036 037 /** The ametys home directory */ 038 private File _directory; 039 040 /** The lock file within the directory */ 041 private File _file; 042 043 /** The random access file. */ 044 private RandomAccessFile _randomAccessFile; 045 046 /** 047 * Unique identifier (canonical path name) of the locked directory. 048 * Used to ensure exclusive locking within a single JVM. 049 */ 050 private String _identifier; 051 052 /** 053 * The file lock. Used to ensure exclusive locking across process 054 * boundaries. 055 */ 056 private FileLock _lock; 057 058 /** 059 * Default constructor. 060 * Initialize the instance. The lock still needs to be 061 * explicitly acquired using the {@link #acquire()} method. 062 * @param ametysHome The ametys home directory 063 * @throws AmetysHomeLockException if the canonical path of the directory can not be determined 064 */ 065 public AmetysHomeLock(File ametysHome) throws AmetysHomeLockException 066 { 067 try 068 { 069 _directory = ametysHome.getCanonicalFile(); 070 _file = new File(_directory, __LOCK_FILE); 071 _identifier = (AmetysHomeLock.class.getName() + ":" + _directory.getPath()).intern(); 072 _lock = null; 073 } 074 catch (IOException e) 075 { 076 throw new AmetysHomeLockException("Unable to construct the Ametys home lock instance at path " + ametysHome.getPath(), e); 077 } 078 } 079 080 /** 081 * Lock the Ametys home directory 082 * @throws AmetysHomeLockException if the repository lock can not be acquired 083 */ 084 public void acquire() throws AmetysHomeLockException 085 { 086 if (_file.exists()) 087 { 088 System.out.println("[WARN] Existing lock file " + _file + " detected. Previous lock was not released properly."); 089 } 090 try 091 { 092 _tryLock(); 093 } 094 catch (AmetysHomeLockException e) 095 { 096 _closeRandomAccessFile(); 097 throw e; 098 } 099 } 100 101 /** 102 * Try to lock the random access file. 103 * @throws AmetysHomeLockException If an error occurs during the lock attempt. 104 */ 105 private void _tryLock() throws AmetysHomeLockException 106 { 107 try 108 { 109 _randomAccessFile = new RandomAccessFile(_file, "rw"); 110 _lock = _randomAccessFile.getChannel().tryLock(); 111 } 112 catch (IOException e) 113 { 114 throw new AmetysHomeLockException("Unable to create or lock file " + _file, e); 115 } 116 catch (OverlappingFileLockException e) 117 { 118 // OverlappingFileLockException with JRE 1.6 119 throw new AmetysHomeLockException("The Ametys home " + _directory + " appears to be in use since the file named " + _file.getName() 120 + " is already locked by the current process."); 121 } 122 123 if (_lock == null) 124 { 125 throw new AmetysHomeLockException("The Ametys home " + _directory + " appears to be in use since the file named " + _file.getName() 126 + " is locked by another process."); 127 } 128 129 // due to a bug in java 1.4/1.5 on *nix platforms 130 // it's possible that java.nio.channels.FileChannel.tryLock() 131 // returns a non-null FileLock object although the lock is already 132 // held by *this* jvm process 133 synchronized (_identifier) 134 { 135 if (null != System.getProperty(_identifier)) 136 { 137 // note that the newly acquired (redundant) file lock 138 // is deliberately *not* released because this could 139 // potentially cause, depending on the implementation, 140 // the previously acquired lock(s) to be released 141 // as well 142 throw new AmetysHomeLockException("The Ametys home " + _directory + " appears to be" + " already locked by the current process."); 143 } 144 else 145 { 146 try 147 { 148 System.setProperty(_identifier, _identifier); 149 } 150 catch (SecurityException e) 151 { 152 System.out.println("[WARN] Unable to set system property: " + _identifier); 153 e.printStackTrace(); 154 } 155 } 156 } 157 } 158 159 /** 160 * Close the random access file if it is open, and set it to null. 161 */ 162 private void _closeRandomAccessFile() 163 { 164 if (_randomAccessFile != null) 165 { 166 try 167 { 168 _randomAccessFile.close(); 169 } 170 catch (IOException e) 171 { 172 System.out.println("[WARN] Unable to close the random access file " + _file); 173 e.printStackTrace(); 174 } 175 _randomAccessFile = null; 176 } 177 } 178 179 /** 180 * Releases repository lock. 181 */ 182 public void release() 183 { 184 if (_lock != null) 185 { 186 try 187 { 188 try (FileChannel channel = _lock.channel()) 189 { 190 _lock.release(); 191 } 192 } 193 catch (IOException e) 194 { 195 // ignore 196 } 197 198 _lock = null; 199 _closeRandomAccessFile(); 200 } 201 202 if (!_file.delete()) 203 { 204 System.out.println("[WARN] Unable to delete repository lock file"); 205 } 206 207 // see #acquire() 208 synchronized (_identifier) 209 { 210 try 211 { 212 System.getProperties().remove(_identifier); 213 } 214 catch (SecurityException e) 215 { 216 System.out.println("[WARN] Unable to clear system property: " + _identifier); 217 e.printStackTrace(); 218 } 219 } 220 } 221}