001: /*
002: * $Id: DiskDatabase.java,v 1.30 2007/11/13 19:04:02 rwald Exp $
003: * =======================================================================
004: * Copyright (c) 2002-2004 Axion Development Team. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above
011: * copyright notice, this list of conditions and the following
012: * disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The names "Tigris", "Axion", nor the names of its contributors may
020: * not be used to endorse or promote products derived from this
021: * software without specific prior written permission.
022: *
023: * 4. Products derived from this software may not be called "Axion", nor
024: * may "Tigris" or "Axion" appear in their names without specific prior
025: * written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
028: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
030: * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
031: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
032: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
033: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
034: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
035: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
036: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
037: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
038: * =======================================================================
039: */
040:
041: package org.axiondb.engine;
042:
043: import java.io.BufferedInputStream;
044: import java.io.DataInputStream;
045: import java.io.DataOutputStream;
046: import java.io.File;
047: import java.io.FileInputStream;
048: import java.io.FileOutputStream;
049: import java.io.FileWriter;
050: import java.io.FilenameFilter;
051: import java.io.IOException;
052: import java.io.InputStream;
053: import java.io.ObjectInputStream;
054: import java.util.ArrayList;
055: import java.util.Iterator;
056: import java.util.List;
057: import java.util.Properties;
058:
059: import org.apache.commons.logging.Log;
060: import org.apache.commons.logging.LogFactory;
061: import org.axiondb.AxionException;
062: import org.axiondb.Database;
063: import org.axiondb.Sequence;
064: import org.axiondb.Table;
065: import org.axiondb.TableFactory;
066: import org.axiondb.engine.commands.AlterTableCommand;
067: import org.axiondb.engine.tables.BaseDiskTable;
068: import org.axiondb.engine.tables.MemoryTable;
069: import org.axiondb.engine.tables.TableViewFactory;
070: import org.axiondb.io.FileUtil;
071:
072: /**
073: * A disk-resident {@link org.axiondb.Database}.
074: *
075: * @version $Revision: 1.30 $ $Date: 2007/11/13 19:04:02 $
076: * @author Chuck Burdick
077: * @author Rodney Waldhoff
078: * @author Morgan Delagrange
079: * @author Ahimanikya Satapathy
080: */
081: public class DiskDatabase extends BaseDatabase implements Database {
082:
083: //------------------------------------------------------------- Constructors
084:
085: public DiskDatabase(File dbDir) throws AxionException {
086: this (dbDir.getName(), dbDir);
087: }
088:
089: public DiskDatabase(String name, File dbDir) throws AxionException {
090: this (name, dbDir, null);
091: }
092:
093: public DiskDatabase(String name, File dbDir, Properties props)
094: throws AxionException {
095: super (name);
096: if (null == dbDir) {
097: throw new AxionException("Database directory required.");
098: }
099:
100: _dbDir = dbDir;
101: _log.debug("Constructing disk-based database in " + dbDir);
102:
103: if (!dbDir.exists()) {
104: dbDir.mkdirs();
105: }
106:
107: if (!dbDir.exists() || !dbDir.isDirectory()) {
108: throw new AxionException("Database directory \"" + dbDir
109: + "\" could not be created or is not a directory.");
110: }
111:
112: props = loadProperties(dbDir, props);
113: obtainLockFile(props);
114: obtainDBVersion();
115: migrate(_dbVersion);
116:
117: createMetaDataTables();
118:
119: // TODO: Seems like this should go after loadProperties(dbDir,props), but that
120: // breaks a unit test
121: loadProperties(props);
122:
123: loadTables(_dbDir);
124: loadSequences();
125:
126: if (!isReadOnly()) {
127: writeDbVersion();
128: }
129: _log.debug("Disk-based database construction successful");
130: }
131:
132: private void writeDbVersion() throws AxionException {
133: File verFile = getDbFileName(".VER");
134: DataOutputStream out = null;
135: try {
136: out = new DataOutputStream(new FileOutputStream(verFile));
137: out.writeInt(AXION_DB_VERSION);
138: out.flush();
139: } catch (IOException e) {
140: String msg = "Unable to persist version file";
141: _log.error(msg, e);
142: throw new AxionException(msg);
143: } finally {
144: closeOutputStream(out);
145: }
146: }
147:
148: protected File getDbFileName(String extension) {
149: return new File(_dbDir, getName().toUpperCase() + extension);
150: }
151:
152: public void checkpoint() throws AxionException {
153: super .checkpoint();
154: if (getSequenceCount() != 0) {
155: File seqFile = getDbFileName(".SEQ");
156: DataOutputStream out = null;
157: try {
158: out = new DataOutputStream(
159: new FileOutputStream(seqFile));
160: out.writeInt(getSequenceCount());
161:
162: for (Iterator i = getSequences(); i.hasNext();) {
163: Sequence cur = (Sequence) (i.next());
164: cur.write(out);
165: }
166: out.flush();
167: } catch (IOException e) {
168: String msg = "Unable to persist sequence file";
169: _log.error(msg, e);
170: throw new AxionException(msg);
171: } finally {
172: closeOutputStream(out);
173: }
174: }
175: }
176:
177: private void closeOutputStream(DataOutputStream out) {
178: if (out != null) {
179: try {
180: out.close();
181: } catch (Exception e) {
182: }
183: }
184: }
185:
186: public void createSequence(Sequence seq) throws AxionException {
187: super .createSequence(seq);
188: checkpoint();
189: }
190:
191: public void defrag() throws AxionException {
192: checkpoint();
193:
194: // Copying to local List to avoid ConcurrentModificationException
195: Iterator i = getTables();
196: List<String> tablesToDefrag = new ArrayList<String>();
197: while (i.hasNext()) {
198: Table table = (Table) (i.next());
199: if (table instanceof BaseDiskTable) {
200: tablesToDefrag.add(table.getName());
201: }
202: }
203:
204: for (Iterator t = tablesToDefrag.iterator(); t.hasNext();) {
205: defragTable((String) t.next());
206: }
207: }
208:
209: public int defragTable(String tableName) throws AxionException {
210: try {
211: AlterTableCommand alterTableCmd = new AlterTableCommand(
212: tableName, false);
213: return alterTableCmd.executeUpdate(this );
214: } catch (AxionException e) {
215: throw new AxionException("Unable to defrag table "
216: + tableName, e);
217: }
218: }
219:
220: public File getDBDirectory() {
221: return _dbDir;
222: }
223:
224: public TableFactory getTableFactory(String name) {
225: if (null == name || "default".equals(name)) {
226: return DEFAULT_TABLE_FACTORY;
227: }
228: return super .getTableFactory(name);
229: }
230:
231: public void migrate(int version) throws AxionException {
232: if (version != -1 && version < 131) { // 0.3.1
233: try {
234: FileUtil.renameToUpperCase(getDBDirectory());
235: _dbVersion = AXION_DB_VERSION;
236: writeDbVersion();
237: } catch (IOException e) {
238: throw new AxionException(e);
239: }
240: }
241: }
242:
243: public void remount(File newdir) throws AxionException {
244: if (_log.isDebugEnabled()) {
245: _log.debug("Remounting from " + _dbDir + " to " + newdir);
246: }
247: _dbDir = newdir;
248: super .remount(newdir);
249: }
250:
251: public void shutdown() throws AxionException {
252: super .shutdown();
253: releaseLockFile();
254: }
255:
256: protected Table createSystemTable(String name) {
257: return new MemoryTable(name, Table.SYSTEM_TABLE_TYPE);
258: }
259:
260: private Properties loadProperties(File dbDir, Properties props) {
261: if (null == props) {
262: if (null != getBaseProperties()) {
263: props = new Properties(getBaseProperties());
264: } else {
265: props = new Properties();
266: }
267: File propfile = new File(dbDir, "axiondb.properties");
268: if (propfile.exists()) {
269: _log.debug("Loading properties from \"" + propfile
270: + "\".");
271: InputStream in = null;
272: try {
273: in = new FileInputStream(propfile);
274: props.load(in);
275: } catch (Exception e) {
276: // PROPOGATE UP!?!
277: _log.error(
278: "Exception while loading properties from \""
279: + propfile + "\".", e);
280: } finally {
281: try {
282: in.close();
283: } catch (Exception e) {
284: }
285: }
286: }
287: }
288: return props;
289: }
290:
291: private void obtainDBVersion() throws AxionException {
292: File verFile = getDbFileName(".VER");
293: if (!verFile.exists()) {
294: verFile = new File(_dbDir, getName() + ".ver"); // old db
295: }
296: if (verFile.exists()) {
297: DataInputStream in = null;
298: try {
299: in = new DataInputStream(new FileInputStream(verFile));
300: _dbVersion = in.readInt();
301: } catch (IOException e) {
302: String msg = "Unable to read db version file";
303: _log.error(msg, e);
304: throw new AxionException(msg);
305: } finally {
306: closeInputStream(in);
307: }
308: }
309: }
310:
311: private void closeInputStream(DataInputStream in) {
312: if (in != null) {
313: try {
314: in.close();
315: } catch (Exception e) {
316: }
317: }
318: }
319:
320: private void loadSequences() throws AxionException {
321: File seqFile = getDbFileName(".SEQ");
322: if (seqFile.exists()) {
323: DataInputStream in = null;
324: try {
325: in = new DataInputStream(new FileInputStream(seqFile));
326: int size = in.readInt();
327:
328: for (int i = 0; i < size; i++) {
329: Sequence seq;
330: if (_dbVersion > 102) {
331: seq = new Sequence();
332: seq.read(in);
333: } else {
334: String name = in.readUTF();
335: int value = in.readInt();
336: seq = new Sequence(name, value);
337: }
338: super .createSequence(seq);
339: }
340: } catch (Exception e) {
341: String msg = "Unable to read sequence file";
342: _log.error(msg, e);
343: throw new AxionException(msg, e);
344: } finally {
345: closeInputStream(in);
346: }
347: }
348: }
349:
350: private void loadTables(File parentdir) throws AxionException {
351: String[] tables = parentdir.list(new FilenameFilter() {
352: public boolean accept(File dir, String name) {
353: File file = new File(dir, name);
354: if (file.isDirectory()) {
355: File idx = new File(file, name + ".TYPE");
356: if (idx.exists()) {
357: return true;
358: }
359: }
360: return false;
361: }
362: });
363: List<String> views = new ArrayList<String>();
364:
365: for (int i = 0; i < tables.length; i++) {
366: if (_log.isDebugEnabled()) {
367: _log.debug("Recreating table " + tables[i]);
368: }
369: File tabledir = new File(parentdir, tables[i]);
370: File typefile = new File(tabledir, tables[i] + ".TYPE");
371: String factoryname = null;
372: ObjectInputStream in = null;
373: try {
374: in = new ObjectInputStream(new BufferedInputStream(
375: new FileInputStream(typefile)));
376: factoryname = in.readUTF();
377: } catch (IOException e) {
378: throw new AxionException(e);
379: } finally {
380: try {
381: in.close();
382: } catch (Exception e) {
383: }
384: }
385: TableFactory factory = null;
386: try {
387: Class clazz = Class.forName(factoryname);
388: factory = (TableFactory) (clazz.newInstance());
389: } catch (Exception e) {
390: throw new AxionException(e);
391: }
392:
393: // load view after loading all tables
394: if (factory instanceof TableViewFactory) {
395: views.add(tabledir.getName());
396: } else {
397: Table table = factory.createTable(this , tabledir
398: .getName());
399: addTable(table);
400: }
401: }
402:
403: // loading views
404: Iterator itr = views.iterator();
405: TableViewFactory factory = new TableViewFactory();
406: while (itr.hasNext()) {
407: Table table = factory
408: .createTable(this , (String) itr.next());
409: addTable(table);
410: }
411: }
412:
413: private void obtainLockFile(Properties props) throws AxionException {
414: String lockFileIgnoreProp = System
415: .getProperty(IGNORE_LOCK_FILE_PROPERTY_NAME);
416: if (lockFileIgnoreProp == null) {
417: _ignoreLockFile = Boolean
418: .valueOf(
419: props
420: .getProperty(DATABASE_LOCKFILE_IGNORE_PROPERTY_NAME))
421: .booleanValue();
422: } else {
423: _ignoreLockFile = Boolean.valueOf(lockFileIgnoreProp)
424: .booleanValue();
425: }
426: if (isReadOnly()) {
427: _ignoreLockFile = true;
428: }
429: if (!_ignoreLockFile) {
430: File lock = new File(_dbDir, LOCK_FILE_NAME);
431: if (lock.exists()) {
432: throw new AxionException(
433: "The database directory at "
434: + _dbDir.getAbsolutePath()
435: + " appears to already be in use by another process. "
436: + " If you feel you have reached this message in error, "
437: + "delete the file at "
438: + lock.getAbsolutePath() + ".", 8002);
439: }
440:
441: lock.deleteOnExit();
442: FileWriter out = null;
443: try {
444: out = new FileWriter(lock);
445: out.write("lock");
446: out.write("\t");
447: out.write(getName());
448: out.write("\t");
449: out.write(String.valueOf(System.currentTimeMillis()));
450: out.flush();
451: } catch (IOException e) {
452: _log.warn(
453: "Unable to create lock file at "
454: + lock.getAbsolutePath()
455: + " due to exception.", e);
456: } finally {
457: try {
458: out.close();
459: } catch (Exception e) {
460: }
461: }
462: }
463: }
464:
465: private void releaseLockFile() {
466: if (!_ignoreLockFile) {
467: File lock = new File(_dbDir, LOCK_FILE_NAME);
468: if (lock.exists()) {
469: if (!lock.delete()) {
470: _log.warn("Unable to delete lock file at "
471: + lock.getAbsolutePath()
472: + " due to exception.");
473: }
474: }
475: }
476: }
477:
478: //-------------------------------------------------------------- Attributes
479:
480: private static final TableFactory DEFAULT_TABLE_FACTORY = new DiskTableFactory();
481: private static final String IGNORE_LOCK_FILE_PROPERTY_NAME = "org.axiondb.engine.DiskDatabase.IGNORE_LOCK_FILE";
482: private static final String LOCK_FILE_NAME = "lockfile.txt";
483: private File _dbDir = null;
484: private boolean _ignoreLockFile = false;
485: private static final int DB_MAJOR_VERSION = 0;
486: private static final int DB_MINOR_VERSION = 3; // XXX CHANGE ME ON RELEASE XXX
487: private static final int DB_INTERNAL_MINOR_VERSION = 1; // XXX RESET TO 0 ON RELEASE
488: private static final int AXION_DB_VERSION = (100 * (DB_MAJOR_VERSION + 1))
489: + (10 * DB_MINOR_VERSION) + DB_INTERNAL_MINOR_VERSION;
490: private int _dbVersion = -1;
491:
492: private static Log _log = LogFactory.getLog(DiskDatabase.class);
493: private static final String DATABASE_LOCKFILE_IGNORE_PROPERTY_NAME = "database.lockfile.ignore";
494: }
|