001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.db;
031:
032: import com.caucho.db.sql.Parser;
033: import com.caucho.db.sql.Query;
034: import com.caucho.db.store.BlockManager;
035: import com.caucho.db.store.Lock;
036: import com.caucho.db.store.Store;
037: import com.caucho.db.table.Table;
038: import com.caucho.db.table.TableFactory;
039: import com.caucho.lifecycle.Lifecycle;
040: import com.caucho.loader.CloseListener;
041: import com.caucho.loader.Environment;
042: import com.caucho.log.Log;
043: import com.caucho.sql.SQLExceptionWrapper;
044: import com.caucho.util.L10N;
045: import com.caucho.util.LruCache;
046: import com.caucho.vfs.Path;
047:
048: import javax.annotation.PostConstruct;
049: import java.io.IOException;
050: import java.sql.SQLException;
051: import java.util.HashMap;
052: import java.util.logging.Level;
053: import java.util.logging.Logger;
054:
055: /**
056: * Manager for a basic Java-based database.
057: */
058: public class Database {
059: private static final Logger log = Logger.getLogger(Database.class
060: .getName());
061: private static final L10N L = new L10N(Database.class);
062:
063: private Path _dir;
064:
065: private BlockManager _blockManager;
066: private HashMap<String, Table> _tables = new HashMap<String, Table>();
067:
068: private LruCache<String, Query> _cachedQueries = new LruCache<String, Query>(
069: 128);
070:
071: private Lock _databaseLock = new Lock("db");
072:
073: private boolean _removeOnError;
074:
075: private long _timeout = 1000L;
076:
077: private final Lifecycle _lifecycle = new Lifecycle(log, null,
078: Level.FINER);
079:
080: /**
081: * Creates the database.
082: */
083: public Database() {
084: this (null);
085: }
086:
087: /**
088: * Creates the database.
089: */
090: public Database(Path dir) {
091: Environment.addClassLoaderListener(new CloseListener(this ));
092:
093: _lifecycle.setName(toString());
094:
095: if (dir != null)
096: setPath(dir);
097:
098: long minSize = 8 * 1024 * 1024;
099:
100: long memorySize = Runtime.getRuntime().maxMemory() / 16;
101:
102: if (minSize < memorySize)
103: minSize = memorySize;
104:
105: _blockManager = BlockManager
106: .create((int) (minSize / Store.BLOCK_SIZE));
107: }
108:
109: /**
110: * Sets the directory.
111: */
112: public void setPath(Path dir) {
113: _dir = dir;
114: }
115:
116: /**
117: * Set if error tables should be removed.
118: */
119: public void setRemoveOnError(boolean remove) {
120: _removeOnError = remove;
121: }
122:
123: /**
124: * Ensure a minimum memory size.
125: *
126: * @param minCapacity the minimum capacity in bytes
127: */
128: public void ensureMemoryCapacity(long minCapacity) {
129: int minBlocks = (int) ((minCapacity + Store.BLOCK_SIZE - 1) / Store.BLOCK_SIZE);
130:
131: _blockManager.ensureCapacity(minBlocks);
132: }
133:
134: /**
135: * Initializes the database. All *.db files in the database directory
136: * are read for stored tables.
137: */
138: @PostConstruct
139: public void init() throws SQLException {
140: if (!_lifecycle.toActive())
141: return;
142:
143: Path dir = _dir;
144:
145: if (dir != null) {
146: String[] list = null;
147:
148: try {
149: list = dir.list();
150: } catch (IOException e) {
151: log.log(Level.WARNING, e.toString(), e);
152: }
153:
154: for (int i = 0; i < list.length; i++) {
155: String name = list[i];
156:
157: if (!name.endsWith(".db"))
158: continue;
159:
160: name = name.substring(0, name.length() - 3);
161:
162: try {
163: Table table = Table.loadFromFile(this , name);
164:
165: table.init();
166:
167: _tables.put(name, table);
168: } catch (Throwable e) {
169: if (_removeOnError) {
170: if (log.isLoggable(Level.FINER))
171: log.log(Level.FINER, e.toString(), e);
172: else
173: log.warning(e.toString());
174:
175: try {
176: dir.lookup(name + ".db").remove();
177: } catch (IOException e1) {
178: }
179: } else
180: throw new SQLExceptionWrapper(e);
181: }
182: }
183: }
184: }
185:
186: /**
187: * Returns the path.
188: */
189: public Path getPath() {
190: return _dir;
191: }
192:
193: /**
194: * Returns the block manager.
195: */
196: public BlockManager getBlockManager() {
197: return _blockManager;
198: }
199:
200: /**
201: * Returns the database lock.
202: */
203: public Lock getDatabaseLock() {
204: return _databaseLock;
205: }
206:
207: /**
208: * Creates a table factory.
209: */
210: public TableFactory createTableFactory() {
211: return new TableFactory(this );
212: }
213:
214: /**
215: * Adds a table.
216: */
217: public void addTable(Table table) throws IOException {
218: log.fine("adding table " + table.getName());
219:
220: table.init();
221:
222: _tables.put(table.getName(), table);
223: }
224:
225: /**
226: * Gets a table.
227: */
228: public Table getTable(String name) {
229: return _tables.get(name);
230: }
231:
232: /**
233: * Drops a table.
234: */
235: public void dropTable(String name) throws SQLException {
236: Table table = null;
237:
238: synchronized (this ) {
239: table = _tables.get(name);
240: if (table == null)
241: throw new SQLException(
242: L
243: .l(
244: "Table {0} does not exist. DROP TABLE can only drop an existing table.",
245: name));
246:
247: _tables.remove(name);
248:
249: _cachedQueries.clear();
250: }
251:
252: table.remove();
253: }
254:
255: /**
256: * Updates the database.
257: */
258: /*
259: public void update(String sql, Transaction xa)
260: throws SQLException
261: {
262: Query query = parseQuery(sql);
263:
264: query.execute(xa);
265: }
266: */
267:
268: /**
269: * Queries the database.
270: */
271: /*
272: public ResultSetImpl query(String sql, Transaction xa)
273: throws SQLException
274: {
275: Query query = parseQuery(sql);
276:
277: return query.execute(xa);
278: }
279: */
280:
281: /**
282: * Parses a query.
283: */
284: public Query parseQuery(String sql) throws SQLException {
285: // XXX: currently, can't cache because the params are improperly shared
286: /*
287: Query query = _cachedQueries.get(sql);
288:
289: if (query == null) {
290: query = Parser.parse(this, sql);
291: _cachedQueries.put(sql, query);
292: }
293: */
294: if (log.isLoggable(Level.FINER))
295: log.finer(this + ": " + sql);
296:
297: Query query = Parser.parse(this , sql);
298:
299: return query;
300: }
301:
302: /*
303: public void lockRead(Transaction xa, long lockId)
304: throws SQLException
305: {
306: Lock databaseLock = _databaseLock;
307:
308: try {
309: synchronized (databaseLock) {
310: if (xa.hasReadLock(databaseLock))
311: return;
312:
313: databaseLock.lockRead(xa, _timeout);
314:
315: xa.addReadLock(databaseLock);
316: }
317: } catch (SQLException e) {
318: xa.setRollbackOnly(e);
319: }
320: }
321: */
322:
323: /**
324: * Closes the database.
325: */
326: public void close() {
327: if (!_lifecycle.toDestroy())
328: return;
329:
330: for (Table table : _tables.values()) {
331: try {
332: table.close();
333: } catch (Throwable e) {
334: log.log(Level.WARNING, e.toString(), e);
335: }
336: }
337: }
338:
339: public String toString() {
340: return "Database[" + _dir + "]";
341: }
342: }
|