001: /* Copyright (c) 2001-2005, The HSQL Development Group
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the HSQL Development Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package org.hsqldb.persist;
032:
033: import org.hsqldb.Database;
034: import org.hsqldb.HsqlException;
035: import org.hsqldb.NumberSequence;
036: import org.hsqldb.Session;
037: import org.hsqldb.Table;
038: import org.hsqldb.Trace;
039: import org.hsqldb.lib.SimpleLog;
040:
041: // boucherb@users 20030510 - patch 1.7.2 - added cooperative file locking
042:
043: /**
044: * The public interface of logging and cache classes.<p>
045: *
046: * Implements a storage manager wrapper that provides a consistent,
047: * always available interface to storage management for the Database
048: * class, despite the fact not all Database objects actually use file
049: * storage.<p>
050: *
051: * The Logger class makes it possible to avoid testing for a
052: * null Log Database attribute again and again, in many different places,
053: * and generally avoids tight coupling between Database and Log, opening
054: * the doors for multiple logs/caches in the future. In this way, the
055: * Database class does not need to know the details of the Logging/Cache
056: * implementation, lowering its breakability factor and promoting
057: * long-term code flexibility.
058: *
059: * @author fredt@users
060: * @version 1.8.0
061: * @since 1.7.0
062: */
063: public class Logger {
064:
065: public SimpleLog appLog;
066:
067: /**
068: * The Log object this Logger object wraps
069: */
070: private Log log;
071:
072: /**
073: * The LockFile object this Logger uses to cooperatively lock
074: * the database files
075: */
076: private LockFile lf;
077: boolean needsCheckpoint;
078: private boolean logStatements;
079: private boolean syncFile = false;
080:
081: public Logger() {
082: appLog = new SimpleLog(null, SimpleLog.LOG_NONE, false);
083: }
084:
085: /**
086: * Opens the specified Database object's database files and starts up
087: * the logging process. <p>
088: *
089: * If the specified Database object is a new database, its database
090: * files are first created.
091: *
092: * @param db the Database
093: * @throws HsqlException if there is a problem, such as the case when
094: * the specified files are in use by another process
095: */
096: public void openLog(Database db) throws HsqlException {
097:
098: needsCheckpoint = false;
099:
100: String path = db.getPath();
101: int loglevel = db.getProperties().getIntegerProperty(
102: HsqlDatabaseProperties.hsqldb_applog, 0);
103:
104: if (loglevel != SimpleLog.LOG_NONE) {
105: appLog = new SimpleLog(path + ".app.log", loglevel, !db
106: .isFilesReadOnly());
107: }
108:
109: appLog.sendLine(SimpleLog.LOG_ERROR, "Database (re)opened");
110:
111: logStatements = false;
112:
113: if (!db.isFilesReadOnly()) {
114: acquireLock(path);
115: }
116:
117: log = new Log(db);
118:
119: log.open();
120:
121: logStatements = !db.isFilesReadOnly();
122: }
123:
124: // fredt@users 20020130 - patch 495484 by boucherb@users
125:
126: /**
127: * Shuts down the logging process using the specified mode. <p>
128: *
129: * @param closemode The mode in which to shut down the logging
130: * process
131: * <OL>
132: * <LI> closemode -1 performs SHUTDOWN IMMEDIATELY, equivalent
133: * to a poweroff or crash.
134: * <LI> closemode 0 performs a normal SHUTDOWN that
135: * checkpoints the database normally.
136: * <LI> closemode 1 performs a shutdown compact that scripts
137: * out the contents of any CACHED tables to the log then
138: * deletes the existing *.data file that contains the data
139: * for all CACHED table before the normal checkpoint process
140: * which in turn creates a new, compact *.data file.
141: * <LI> closemode 2 performs a SHUTDOWN SCRIPT.
142: * </OL>
143: *
144: * @return true if closed with no problems or false if a problem was
145: * encountered.
146: */
147: public boolean closeLog(int closemode) {
148:
149: if (log == null) {
150: appLog.sendLine(SimpleLog.LOG_ERROR, "Database closed");
151: appLog.close();
152:
153: return true;
154: }
155:
156: try {
157: switch (closemode) {
158:
159: case Database.CLOSEMODE_IMMEDIATELY:
160: log.shutdown();
161: break;
162:
163: case Database.CLOSEMODE_NORMAL:
164: log.close(false);
165: break;
166:
167: case Database.CLOSEMODE_COMPACT:
168: case Database.CLOSEMODE_SCRIPT:
169: log.close(true);
170: break;
171: }
172: } catch (Throwable e) {
173: appLog.logContext(e, "error closing log");
174: appLog.close();
175:
176: log = null;
177:
178: return false;
179: }
180:
181: appLog.sendLine(SimpleLog.LOG_ERROR, "Database closed");
182: appLog.close();
183:
184: log = null;
185:
186: return true;
187: }
188:
189: /**
190: * Determines if the logging process actually does anything. <p>
191: *
192: * In-memory Database objects do not need to log anything. This
193: * method is essentially equivalent to testing whether this logger's
194: * database is an in-memory mode database.
195: *
196: * @return true if this object encapsulates a non-null Log instance,
197: * else false
198: */
199: public boolean hasLog() {
200: return log != null;
201: }
202:
203: /**
204: * Returns the Cache object or null if one doesn't exist.
205: */
206: public DataFileCache getCache() throws HsqlException {
207:
208: if (log == null) {
209: return null;
210: } else {
211: return log.getCache();
212: }
213: }
214:
215: /**
216: * Returns the Cache object or null if one doesn't exist.
217: */
218: public boolean hasCache() {
219:
220: if (log == null) {
221: return false;
222: } else {
223: return log.hasCache();
224: }
225: }
226:
227: /**
228: * Records a Log entry representing a new connection action on the
229: * specified Session object.
230: *
231: * @param session the Session object for which to record the log
232: * entry
233: * @throws HsqlException if there is a problem recording the Log
234: * entry
235: */
236: public synchronized void logConnectUser(Session session)
237: throws HsqlException {
238:
239: if (logStatements) {
240: writeToLog(session, session.getUser().getConnectStatement());
241: }
242: }
243:
244: /**
245: * Records a Log entry for the specified SQL statement, on behalf of
246: * the specified Session object.
247: *
248: * @param session the Session object for which to record the Log
249: * entry
250: * @param statement the SQL statement to Log
251: * @throws HsqlException if there is a problem recording the entry
252: */
253: public synchronized void writeToLog(Session session,
254: String statement) throws HsqlException {
255:
256: if (logStatements && log != null) {
257: log.writeStatement(session, statement);
258: }
259: }
260:
261: public synchronized void writeInsertStatement(Session session,
262: Table table, Object[] row) throws HsqlException {
263:
264: if (logStatements) {
265: log.writeInsertStatement(session, table, row);
266: }
267: }
268:
269: public synchronized void writeDeleteStatement(Session session,
270: Table t, Object[] row) throws HsqlException {
271:
272: if (logStatements) {
273: log.writeDeleteStatement(session, t, row);
274: }
275: }
276:
277: public synchronized void writeSequenceStatement(Session session,
278: NumberSequence s) throws HsqlException {
279:
280: if (logStatements) {
281: log.writeSequenceStatement(session, s);
282: }
283: }
284:
285: public synchronized void writeCommitStatement(Session session)
286: throws HsqlException {
287:
288: if (logStatements) {
289: log.writeCommitStatement(session);
290: synchLog();
291: }
292: }
293:
294: /**
295: * Called after commits or after each statement when autocommit is on
296: */
297: public synchronized void synchLog() {
298:
299: if (logStatements && syncFile) {
300: log.synchLog();
301: }
302: }
303:
304: public synchronized void synchLogForce() {
305:
306: if (logStatements) {
307: log.synchLog();
308: }
309: }
310:
311: /**
312: * Checkpoints the database. <p>
313: *
314: * The most important effect of calling this method is to cause the
315: * log file to be rewritten in the most efficient form to
316: * reflect the current state of the database, i.e. only the DDL and
317: * insert DML required to recreate the database in its present state.
318: * Other house-keeping duties are performed w.r.t. other database
319: * files, in order to ensure as much as possible the ACID properites
320: * of the database.
321: *
322: * @throws HsqlException if there is a problem checkpointing the
323: * database
324: */
325: public synchronized void checkpoint(boolean mode)
326: throws HsqlException {
327:
328: if (logStatements) {
329: appLog.logContext(appLog.LOG_NORMAL, "start");
330:
331: needsCheckpoint = false;
332:
333: log.checkpoint(mode);
334: appLog.logContext(appLog.LOG_NORMAL, "end");
335: }
336: }
337:
338: /**
339: * Sets the maximum size to which the log file can grow
340: * before being automatically checkpointed.
341: *
342: * @param megas size in MB
343: */
344: public synchronized void setLogSize(int megas) {
345:
346: if (log != null) {
347: log.setLogSize(megas);
348: }
349: }
350:
351: /**
352: * Sets the type of script file, currently 0 for text (default)
353: * 1 for binary and 3 for compressed
354: *
355: * @param i The type
356: */
357: public synchronized void setScriptType(int i) throws HsqlException {
358:
359: if (log != null) {
360: log.setScriptType(i);
361: }
362: }
363:
364: /**
365: * Sets the log write delay mode to number of seconds. By default
366: * executed commands written to the log are committed fully at most
367: * 60 second after they are executed. This improves performance for
368: * applications that execute a large number
369: * of short running statements in a short period of time, but risks
370: * failing to log some possibly large number of statements in the
371: * event of a crash. A small value improves recovery.
372: * A value of 0 will severly slow down logging when autocommit is on,
373: * or many short transactions are committed.
374: *
375: * @param delay in seconds
376: */
377: public synchronized void setWriteDelay(int delay) {
378:
379: if (log != null) {
380: syncFile = (delay == 0);
381:
382: log.setWriteDelay(delay);
383: }
384: }
385:
386: public int getWriteDelay() {
387: return log != null ? log.getWriteDelay() : 0;
388: }
389:
390: public int getLogSize() {
391: return log != null ? log.getLogSize() : 0;
392: }
393:
394: public int getScriptType() {
395: return log != null ? log.getScriptType() : 0;
396: }
397:
398: /**
399: * Opens the TextCache object.
400: */
401: public DataFileCache openTextCache(Table table, String source,
402: boolean readOnlyData, boolean reversed)
403: throws HsqlException {
404: return log.openTextCache(table, source, readOnlyData, reversed);
405: }
406:
407: /**
408: * Closes the TextCache object.
409: */
410: public void closeTextCache(Table table) throws HsqlException {
411: log.closeTextCache(table);
412: }
413:
414: public boolean needsCheckpoint() {
415: return needsCheckpoint;
416: }
417:
418: /**
419: * Attempts to aquire a cooperative lock condition on the database files
420: */
421: public void acquireLock(String path) throws HsqlException {
422:
423: if (lf != null) {
424: return;
425: }
426:
427: lf = LockFile.newLockFileLock(path);
428: }
429:
430: public void releaseLock() {
431:
432: try {
433: if (lf != null) {
434: lf.tryRelease();
435: }
436: } catch (Exception e) {
437: if (Trace.TRACE) {
438: Trace.printSystemOut(e.toString());
439: }
440: }
441:
442: lf = null;
443: }
444: }
|