001: package liquibase.lock;
002:
003: import liquibase.DatabaseChangeLogLock;
004: import liquibase.util.NetUtil;
005: import liquibase.database.Database;
006: import liquibase.database.sql.RawSqlStatement;
007: import liquibase.database.sql.UpdateStatement;
008: import liquibase.exception.JDBCException;
009: import liquibase.exception.LockException;
010: import liquibase.log.LogFactory;
011:
012: import java.net.InetAddress;
013: import java.net.UnknownHostException;
014: import java.net.NetworkInterface;
015: import java.net.SocketException;
016: import java.sql.Timestamp;
017: import java.text.DateFormat;
018: import java.util.*;
019:
020: public class LockHandler {
021:
022: private Database database;
023: private boolean hasChangeLogLock = false;
024:
025: private long changeLogLockWaitTime = 1000 * 60 * 5; //default to 5 mins
026:
027: private static Map<Database, LockHandler> instances = new HashMap<Database, LockHandler>();
028:
029: private LockHandler(Database database) {
030: this .database = database;
031: }
032:
033: public static LockHandler getInstance(Database database) {
034: if (!instances.containsKey(database)) {
035: instances.put(database, new LockHandler(database));
036: }
037: return instances.get(database);
038: }
039:
040: public boolean acquireLock() throws LockException {
041: if (!database.doesChangeLogLockTableExist()) {
042: throw new LockException(
043: "Could not acquire lock, table does not exist");
044: }
045:
046: try {
047: Boolean locked;
048: try {
049: locked = (Boolean) database.getJdbcTemplate()
050: .queryForObject(
051: database.getSelectChangeLogLockSQL(),
052: Boolean.class);
053: } catch (JDBCException e) {
054: if (!database.getJdbcTemplate().executesStatements()) {
055: //expected
056: locked = false;
057: } else {
058: throw new LockException(
059: "Error checking database lock status", e);
060: }
061: }
062: if (locked) {
063: return false;
064: } else {
065: UpdateStatement updateStatement = new UpdateStatement(
066: database.getDefaultSchemaName(), database
067: .getDatabaseChangeLogLockTableName());
068: updateStatement.addNewColumnValue("LOCKED", true);
069: updateStatement.addNewColumnValue("LOCKGRANTED",
070: new Timestamp(new java.util.Date().getTime()));
071: InetAddress localHost = NetUtil.getLocalHost();
072: updateStatement.addNewColumnValue("LOCKEDBY", localHost
073: .getHostName()
074: + " (" + localHost.getHostAddress() + ")");
075: updateStatement.setWhereClause("ID = 1");
076:
077: database.getJdbcTemplate().comment("Lock Database");
078: int rowsUpdated = database.getJdbcTemplate().update(
079: updateStatement);
080: if (rowsUpdated != 1) {
081: if (!database.getJdbcTemplate()
082: .executesStatements()) {
083: //expected
084: } else {
085: throw new LockException(
086: "Did not update change log lock correctly");
087: }
088: }
089: database.commit();
090: LogFactory.getLogger().info(
091: "Successfully acquired change log lock");
092:
093: hasChangeLogLock = true;
094: return true;
095: }
096: } catch (Exception e) {
097: throw new LockException(e);
098: }
099:
100: }
101:
102: public void releaseLock() throws LockException {
103: if (database.doesChangeLogLockTableExist()) {
104: try {
105: UpdateStatement releaseStatement = new UpdateStatement(
106: database.getDefaultSchemaName(), database
107: .getDatabaseChangeLogLockTableName());
108: releaseStatement.addNewColumnValue("LOCKED", false);
109: releaseStatement.addNewColumnValue("LOCKGRANTED", null);
110: releaseStatement.addNewColumnValue("LOCKEDBY", null);
111: releaseStatement.setWhereClause(" ID = 1");
112:
113: database.getJdbcTemplate().comment(
114: "Release Database Lock");
115: int updatedRows = database.getJdbcTemplate().update(
116: releaseStatement);
117: if (updatedRows != 1) {
118: if (database.getJdbcTemplate().executesStatements()) {
119: throw new LockException(
120: "Did not update change log lock correctly.\n\n"
121: + releaseStatement
122: + " updated "
123: + updatedRows
124: + " instead of the expected 1 row.");
125: }
126: }
127: database.commit();
128: hasChangeLogLock = false;
129:
130: LogFactory.getLogger().info(
131: "Successfully released change log lock");
132: } catch (Exception e) {
133: throw new LockException(e);
134: }
135: }
136: }
137:
138: public DatabaseChangeLogLock[] listLocks() throws LockException {
139: if (!database.doesChangeLogLockTableExist()) {
140: return new DatabaseChangeLogLock[0];
141: }
142:
143: try {
144: List<DatabaseChangeLogLock> allLocks = new ArrayList<DatabaseChangeLogLock>();
145: RawSqlStatement sqlStatement = new RawSqlStatement(
146: (("SELECT ID, LOCKED, LOCKGRANTED, LOCKEDBY FROM " + database
147: .escapeTableName(
148: database.getDefaultSchemaName(),
149: database
150: .getDatabaseChangeLogLockTableName()))));
151: List<Map> rows = database.getJdbcTemplate().queryForList(
152: sqlStatement);
153: for (Map columnMap : rows) {
154: Boolean locked = (Boolean) columnMap.get("LOCKED");
155: if (locked != null && locked) {
156: allLocks.add(new DatabaseChangeLogLock(
157: (Integer) columnMap.get("ID"),
158: (Date) columnMap.get("LOCKGRANTED"),
159: (String) columnMap.get("LOCKEDBY")));
160: }
161: }
162: return allLocks.toArray(new DatabaseChangeLogLock[allLocks
163: .size()]);
164: } catch (Exception e) {
165: throw new LockException(e);
166: }
167: }
168:
169: public void waitForLock() throws LockException {
170: if (hasChangeLogLock) {
171: return;
172: }
173:
174: try {
175: database.checkDatabaseChangeLogLockTable();
176:
177: boolean locked = false;
178: long timeToGiveUp = new Date().getTime()
179: + changeLogLockWaitTime;
180: while (!locked && new Date().getTime() < timeToGiveUp) {
181: locked = acquireLock();
182: if (!locked) {
183: System.out
184: .println("Waiting for changelog lock....");
185: try {
186: Thread.sleep(1000 * 10);
187: } catch (InterruptedException e) {
188: ;
189: }
190: }
191: }
192:
193: if (!locked) {
194: DatabaseChangeLogLock[] locks = listLocks();
195: String lockedBy;
196: if (locks.length > 0) {
197: DatabaseChangeLogLock lock = locks[0];
198: lockedBy = lock.getLockedBy()
199: + " since "
200: + DateFormat.getDateTimeInstance(
201: DateFormat.SHORT, DateFormat.SHORT)
202: .format(lock.getLockGranted());
203: } else {
204: lockedBy = "UNKNOWN";
205: }
206: throw new LockException(
207: "Could not acquire change log lock. Currently locked by "
208: + lockedBy);
209: }
210: } catch (JDBCException e) {
211: if (!database.getJdbcTemplate().executesStatements()) {
212: ; //nothing to do
213: } else {
214: throw new LockException(e);
215: }
216: }
217: }
218:
219: /**
220: * Releases whatever locks are on the database change log table
221: */
222: public void forceReleaseLock() throws LockException, JDBCException {
223: database.checkDatabaseChangeLogLockTable();
224:
225: releaseLock();
226: }
227:
228: }
|