001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.store.fs;
007:
008: import java.io.ByteArrayInputStream;
009: import java.io.ByteArrayOutputStream;
010: import java.io.IOException;
011: import java.io.InputStream;
012: import java.io.OutputStream;
013: import java.sql.Connection;
014: import java.sql.PreparedStatement;
015: import java.sql.ResultSet;
016: import java.sql.SQLException;
017: import java.sql.Statement;
018: import java.util.ArrayList;
019: import java.util.HashMap;
020: import java.util.Properties;
021:
022: import org.h2.Driver;
023: import org.h2.message.Message;
024: import org.h2.util.IOUtils;
025: import org.h2.util.JdbcUtils;
026: import org.h2.util.StringUtils;
027:
028: /**
029: * This file system stores everything in a database.
030: */
031: public class FileSystemDatabase extends FileSystem {
032:
033: private Connection conn;
034: private String url;
035: private static final HashMap INSTANCES = new HashMap();
036: private HashMap preparedMap = new HashMap();
037: private boolean log;
038:
039: public static synchronized FileSystem getInstance(String url) {
040: int idx = url.indexOf('/');
041: if (idx > 0) {
042: url = url.substring(0, idx);
043: }
044: FileSystemDatabase fs = (FileSystemDatabase) INSTANCES.get(url);
045: if (fs != null) {
046: return fs;
047: }
048: Connection conn;
049: try {
050: if (url.startsWith("jdbc:h2:")) {
051: // avoid using DriverManager if possible
052: conn = Driver.load().connect(url, new Properties());
053: } else {
054: conn = JdbcUtils.getConnection(null, url,
055: new Properties());
056: }
057: boolean log = url.toUpperCase().indexOf("TRACE_") >= 0;
058: fs = new FileSystemDatabase(url, conn, log);
059: INSTANCES.put(url, fs);
060: return fs;
061: } catch (SQLException e) {
062: throw Message.convertToInternal(e);
063: }
064: }
065:
066: public void close() {
067: JdbcUtils.closeSilently(conn);
068: }
069:
070: private FileSystemDatabase(String url, Connection conn, boolean log)
071: throws SQLException {
072: this .url = url;
073: this .conn = conn;
074: this .log = log;
075: Statement stat = conn.createStatement();
076: conn.setAutoCommit(false);
077: stat.execute("SET ALLOW_LITERALS NONE");
078: stat.execute("CREATE TABLE IF NOT EXISTS FILES("
079: + "ID IDENTITY, PARENTID BIGINT, NAME VARCHAR, "
080: + "LASTMODIFIED BIGINT, LENGTH BIGINT, "
081: + "UNIQUE(PARENTID, NAME))");
082: stat.execute("CREATE TABLE IF NOT EXISTS FILEDATA("
083: + "ID BIGINT PRIMARY KEY, DATA BLOB)");
084: PreparedStatement prep = conn
085: .prepareStatement("SET MAX_LENGTH_INPLACE_LOB ?");
086: prep.setLong(1, 4096);
087: prep.execute();
088: stat
089: .execute("MERGE INTO FILES VALUES(ZERO(), NULL, SPACE(ZERO()), ZERO(), NULL)");
090: commit();
091: if (log) {
092: ResultSet rs = stat
093: .executeQuery("SELECT * FROM FILES ORDER BY PARENTID, NAME");
094: while (rs.next()) {
095: long id = rs.getLong("ID");
096: long parentId = rs.getLong("PARENTID");
097: String name = rs.getString("NAME");
098: long lastModified = rs.getLong("LASTMODIFIED");
099: long length = rs.getLong("LENGTH");
100: log(id + " " + name + " parent:" + parentId
101: + " length:" + length + " lastMod:"
102: + lastModified);
103: }
104: }
105: }
106:
107: private void commit() {
108: try {
109: conn.commit();
110: } catch (SQLException e) {
111: if (log) {
112: e.printStackTrace();
113: }
114: }
115: }
116:
117: private void rollback() {
118: try {
119: conn.rollback();
120: } catch (SQLException e) {
121: if (log) {
122: e.printStackTrace();
123: }
124: }
125: }
126:
127: private void log(String s) {
128: if (log) {
129: System.out.println(s);
130: }
131: }
132:
133: private long getId(String fileName, boolean parent) {
134: fileName = translateFileName(fileName);
135: log(fileName);
136: try {
137: String[] path = StringUtils
138: .arraySplit(fileName, '/', false);
139: long id = 0;
140: int len = parent ? path.length - 1 : path.length;
141: if (fileName.endsWith("/")) {
142: len--;
143: }
144: for (int i = 1; i < len; i++) {
145: PreparedStatement prep = prepare("SELECT ID FROM FILES WHERE PARENTID=? AND NAME=?");
146: prep.setLong(1, id);
147: prep.setString(2, path[i]);
148: ResultSet rs = prep.executeQuery();
149: if (!rs.next()) {
150: return -1;
151: }
152: id = rs.getLong(1);
153: }
154: return id;
155: } catch (SQLException e) {
156: throw convert(e);
157: }
158: }
159:
160: private String translateFileName(String fileName) {
161: if (fileName.startsWith(url)) {
162: fileName = fileName.substring(url.length());
163: }
164: return fileName;
165: }
166:
167: private PreparedStatement prepare(String sql) throws SQLException {
168: PreparedStatement prep = (PreparedStatement) preparedMap
169: .get(sql);
170: if (prep == null) {
171: prep = conn.prepareStatement(sql);
172: preparedMap.put(sql, prep);
173: }
174: return prep;
175: }
176:
177: private RuntimeException convert(SQLException e) {
178: if (log) {
179: e.printStackTrace();
180: }
181: return new RuntimeException(e.toString());
182: }
183:
184: public boolean canWrite(String fileName) {
185: return true;
186: }
187:
188: public void copy(String original, String copy) throws SQLException {
189: try {
190: OutputStream out = openFileOutputStream(copy, false);
191: InputStream in = openFileInputStream(original);
192: IOUtils.copyAndClose(in, out);
193: } catch (IOException e) {
194: rollback();
195: throw Message.convertIOException(e, "Can not copy "
196: + original + " to " + copy);
197: }
198: }
199:
200: public void createDirs(String fileName) throws SQLException {
201: fileName = translateFileName(fileName);
202: try {
203: String[] path = StringUtils
204: .arraySplit(fileName, '/', false);
205: long parentId = 0;
206: int len = path.length;
207: if (fileName.endsWith("/")) {
208: len--;
209: }
210: len--;
211: for (int i = 1; i < len; i++) {
212: PreparedStatement prep = prepare("SELECT ID FROM FILES WHERE PARENTID=? AND NAME=?");
213: prep.setLong(1, parentId);
214: prep.setString(2, path[i]);
215: ResultSet rs = prep.executeQuery();
216: if (!rs.next()) {
217: prep = prepare("INSERT INTO FILES(NAME, PARENTID, LASTMODIFIED) VALUES(?, ?, ?)");
218: prep.setString(1, path[i]);
219: prep.setLong(2, parentId);
220: prep.setLong(3, System.currentTimeMillis());
221: prep.execute();
222: rs = JdbcUtils.getGeneratedKeys(prep);
223: rs.next();
224: parentId = rs.getLong(1);
225: } else {
226: parentId = rs.getLong(1);
227: }
228: }
229: commit();
230: } catch (SQLException e) {
231: rollback();
232: throw convert(e);
233: }
234: }
235:
236: public boolean createNewFile(String fileName) throws SQLException {
237: try {
238: if (exists(fileName)) {
239: return false;
240: }
241: openFileObject(fileName, "rw").close();
242: return true;
243: } catch (IOException e) {
244: throw Message.convert(e);
245: }
246: }
247:
248: public String createTempFile(String name, String suffix,
249: boolean deleteOnExit, boolean inTempDir) throws IOException {
250: name = translateFileName(name);
251: name += ".";
252: for (int i = 0;; i++) {
253: String n = name + i + suffix;
254: if (!exists(n)) {
255: // creates the file (not thread safe)
256: openFileObject(n, "rw").close();
257: return n;
258: }
259: }
260: }
261:
262: public synchronized void delete(String fileName)
263: throws SQLException {
264: try {
265: long id = getId(fileName, false);
266: PreparedStatement prep = prepare("DELETE FROM FILES WHERE ID=?");
267: prep.setLong(1, id);
268: prep.execute();
269: prep = prepare("DELETE FROM FILEDATA WHERE ID=?");
270: prep.setLong(1, id);
271: prep.execute();
272: commit();
273: } catch (SQLException e) {
274: rollback();
275: throw convert(e);
276: }
277: }
278:
279: public void deleteRecursive(String fileName) throws SQLException {
280: throw Message.getUnsupportedException();
281: }
282:
283: public boolean exists(String fileName) {
284: long id = getId(fileName, false);
285: return id >= 0;
286: }
287:
288: public boolean fileStartsWith(String fileName, String prefix) {
289: fileName = translateFileName(fileName);
290: return fileName.startsWith(prefix);
291: }
292:
293: public String getAbsolutePath(String fileName) {
294: return fileName;
295: }
296:
297: public String getFileName(String fileName) throws SQLException {
298: fileName = translateFileName(fileName);
299: String[] path = StringUtils.arraySplit(fileName, '/', false);
300: return path[path.length - 1];
301: }
302:
303: public synchronized long getLastModified(String fileName) {
304: try {
305: long id = getId(fileName, false);
306: PreparedStatement prep = prepare("SELECT LASTMODIFIED FROM FILES WHERE ID=?");
307: prep.setLong(1, id);
308: ResultSet rs = prep.executeQuery();
309: rs.next();
310: return rs.getLong(1);
311: } catch (SQLException e) {
312: throw convert(e);
313: }
314: }
315:
316: public String getParent(String fileName) {
317: int idx = Math.max(fileName.indexOf(':'), fileName
318: .lastIndexOf('/'));
319: return fileName.substring(0, idx);
320: }
321:
322: public boolean isAbsolute(String fileName) {
323: return true;
324: }
325:
326: public synchronized boolean isDirectory(String fileName) {
327: try {
328: long id = getId(fileName, false);
329: PreparedStatement prep = prepare("SELECT LENGTH FROM FILES WHERE ID=?");
330: prep.setLong(1, id);
331: ResultSet rs = prep.executeQuery();
332: rs.next();
333: rs.getLong(1);
334: return rs.wasNull();
335: } catch (SQLException e) {
336: throw convert(e);
337: }
338: }
339:
340: public boolean isReadOnly(String fileName) {
341: return false;
342: }
343:
344: public synchronized long length(String fileName) {
345: try {
346: long id = getId(fileName, false);
347: PreparedStatement prep = prepare("SELECT LENGTH FROM FILES WHERE ID=?");
348: prep.setLong(1, id);
349: ResultSet rs = prep.executeQuery();
350: rs.next();
351: return rs.getLong(1);
352: } catch (SQLException e) {
353: throw convert(e);
354: }
355: }
356:
357: public synchronized String[] listFiles(String path)
358: throws SQLException {
359: try {
360: String name = path;
361: if (!name.endsWith("/")) {
362: name += "/";
363: }
364: long id = getId(path, false);
365: PreparedStatement prep = prepare("SELECT NAME FROM FILES WHERE PARENTID=? ORDER BY NAME");
366: prep.setLong(1, id);
367: ResultSet rs = prep.executeQuery();
368: ArrayList list = new ArrayList();
369: while (rs.next()) {
370: list.add(name + rs.getString(1));
371: }
372: String[] result = new String[list.size()];
373: list.toArray(result);
374: return result;
375: } catch (SQLException e) {
376: throw convert(e);
377: }
378: }
379:
380: public String normalize(String fileName) throws SQLException {
381: return fileName;
382: }
383:
384: public InputStream openFileInputStream(String fileName)
385: throws IOException {
386: return new FileObjectInputStream(openFileObject(fileName, "r"));
387: }
388:
389: public FileObject openFileObject(String fileName, String mode)
390: throws IOException {
391: try {
392: long id = getId(fileName, false);
393: PreparedStatement prep = prepare("SELECT DATA FROM FILEDATA WHERE ID=?");
394: prep.setLong(1, id);
395: ResultSet rs = prep.executeQuery();
396: if (rs.next()) {
397: InputStream in = rs.getBinaryStream(1);
398: ByteArrayOutputStream out = new ByteArrayOutputStream();
399: IOUtils.copyAndClose(in, out);
400: byte[] data = out.toByteArray();
401: return new FileObjectDatabase(this , fileName, data,
402: false);
403: } else {
404: return new FileObjectDatabase(this , fileName,
405: new byte[0], true);
406: }
407: } catch (SQLException e) {
408: throw convert(e);
409: }
410: }
411:
412: public OutputStream openFileOutputStream(String fileName,
413: boolean append) throws SQLException {
414: try {
415: return new FileObjectOutputStream(openFileObject(fileName,
416: "rw"), append);
417: } catch (IOException e) {
418: throw Message.convertIOException(e, fileName);
419: }
420: }
421:
422: public synchronized void rename(String oldName, String newName)
423: throws SQLException {
424: try {
425: long parentOld = getId(oldName, true);
426: long parentNew = getId(newName, true);
427: if (parentOld != parentNew) {
428: throw Message.getUnsupportedException();
429: }
430: newName = getFileName(newName);
431: long id = getId(oldName, false);
432: PreparedStatement prep = prepare("UPDATE FILES SET NAME=? WHERE ID=?");
433: prep.setString(1, newName);
434: prep.setLong(2, id);
435: prep.execute();
436: commit();
437: } catch (SQLException e) {
438: rollback();
439: throw convert(e);
440: }
441: }
442:
443: public boolean tryDelete(String fileName) {
444: try {
445: delete(fileName);
446: } catch (SQLException e) {
447: return false;
448: }
449: return true;
450: }
451:
452: synchronized void write(String fileName, byte[] b, int len)
453: throws IOException {
454: try {
455: long id = getId(fileName, false);
456: if (id >= 0) {
457: PreparedStatement prep = prepare("DELETE FROM FILES WHERE ID=?");
458: prep.setLong(1, id);
459: prep.execute();
460: prep = prepare("DELETE FROM FILEDATA WHERE ID=?");
461: prep.setLong(1, id);
462: prep.execute();
463: }
464: long parentId = getId(fileName, true);
465: PreparedStatement prep = prepare("INSERT INTO FILES(PARENTID, NAME, LASTMODIFIED) VALUES(?, ?, ?)");
466: prep.setLong(1, parentId);
467: prep.setString(2, getFileName(fileName));
468: prep.setLong(3, System.currentTimeMillis());
469: prep.execute();
470: ResultSet rs = JdbcUtils.getGeneratedKeys(prep);
471: rs.next();
472: id = rs.getLong(1);
473: prep = prepare("INSERT INTO FILEDATA(ID, DATA) VALUES(?, ?)");
474: prep.setLong(1, id);
475: ByteArrayInputStream in = new ByteArrayInputStream(b, 0,
476: len);
477: prep.setBinaryStream(2, in, -1);
478: prep.execute();
479: prep = prepare("UPDATE FILES SET LENGTH=(SELECT LENGTH(DATA) FROM FILEDATA WHERE ID=?) WHERE ID=?");
480: prep.setLong(1, id);
481: prep.setLong(2, id);
482: prep.execute();
483: commit();
484: } catch (SQLException e) {
485: rollback();
486: throw convert(e);
487: }
488: }
489:
490: }
|