package de.hub.emffrag.mongodb; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import com.mongodb.BasicDBObject; import com.mongodb.DB; import com.mongodb.DBCollection; import com.mongodb.DBCursor; import com.mongodb.DBObject; import com.mongodb.MongoException; import com.mongodb.gridfs.GridFS; import com.mongodb.gridfs.GridFSDBFile; import com.mongodb.gridfs.GridFSInputFile; import de.hub.emffrag.datastore.IBaseDataStore; import de.hub.emffrag.datastore.IScanExtension; import de.hub.emffrag.datastore.URIUtils; public class MongoDBDataStore implements IBaseDataStore, IScanExtension { private static final int MAX_BSON_SIZE = 1024*1024*16 - 256; private static final String TYPE = "type"; private static final int TYPE_BSON = 1; private static final int TYPE_GRID_FS = 2; private static final String VALUE = "value"; private static final String KEY = "key"; private static final String FILE_NAME = "file"; private DBCollection collection; private DB db; private GridFS gridFs; public MongoDBDataStore(String host, String dataStoreId) { this(host, dataStoreId, false); } public MongoDBDataStore(String host, String dataStoreId, boolean dropFirst) { String hostName = null; int hostPort = -1; String[] hostParts = host.split(":"); if (hostParts.length == 1) { hostName = hostParts[0]; } else if (hostParts.length == 2) { hostName = hostParts[0]; try { hostPort = Integer.parseInt(hostParts[1]); } catch (NumberFormatException e) { throw new IllegalArgumentException("Invalid host format: " + host); } } else { throw new IllegalArgumentException("Invalid host format: " + host); } db = EmfFragMongoDBActivator.instance.getDataBase(hostName, hostPort); collection = db.getCollection(dataStoreId); if (dropFirst) { collection.dropIndexes(); collection.drop(); collection = db.getCollection(dataStoreId); } collection.ensureIndex(new BasicDBObject(KEY, 1), new BasicDBObject("unique", true)); gridFs = new GridFS(db); } private byte[] adoptKey(byte[] key) { assert(key.length <= 60); byte[] result = new byte[60]; for (int i = 0; i < 60; i++) { result[i] = i < key.length ? key[i] : 0; } return result; } @Override synchronized public byte[] ceiling(byte[] key) { byte[] keyString = adoptKey(key); DBObject result = collection.findOne(new BasicDBObject(KEY, new BasicDBObject("$gte", keyString)), new BasicDBObject(KEY, ""), new BasicDBObject(KEY, 1)); if (result != null) { return (byte[])result.get(KEY); } else { return null; } } @Override synchronized public byte[] floor(byte[] key) { DBObject result = collection.findOne(new BasicDBObject(KEY, new BasicDBObject("$lte", adoptKey(key))), new BasicDBObject(KEY, ""), new BasicDBObject(KEY, -1)); if (result != null) { return (byte[])result.get(KEY); } else { return null; } } @Override synchronized public InputStream openInputStream(byte[] key) { DBObject result = collection.findOne(new BasicDBObject(KEY, adoptKey(key))); return inputStreamFromDBObject(result); } private InputStream inputStreamFromDBObject(DBObject result) { if (result != null) { Integer typeAsInteger = (Integer)result.get(TYPE); if (typeAsInteger == null) { return null; } int type = typeAsInteger; if (type == TYPE_BSON) { byte[] value = (byte[])result.get(VALUE); if (value != null) { return new ByteArrayInputStream(value); } else { return new ByteArrayInputStream(new byte[]{}); } } else if (type == TYPE_GRID_FS) { String fileName = (String)result.get(FILE_NAME); if (fileName == null) { throw new IllegalStateException("There is no file name."); } GridFSDBFile gridFsFile = gridFs.findOne(fileName); if (gridFsFile == null) { throw new IllegalStateException("There is no grid fs file for the given key."); } return gridFsFile.getInputStream(); } else { throw new IllegalStateException("Unknown mongo-db value storage type."); } } else { return null; } } @Override synchronized public OutputStream openOutputStream(final byte[] key) { return new ByteArrayOutputStream(256) { @Override public void close() throws IOException { super.close(); byte[] keyString = adoptKey(key); byte[] byteArray = toByteArray(); if (byteArray.length < MAX_BSON_SIZE) { collection.update(new BasicDBObject(KEY, keyString), new BasicDBObject(KEY, keyString).append(TYPE, TYPE_BSON).append(VALUE, byteArray), true, false); } else { // do grid fs GridFSInputFile gridFsFile = gridFs.createFile(byteArray); String fileName = URIUtils.encode(key); gridFsFile.setFilename(fileName); gridFsFile.save(); collection.update(new BasicDBObject(KEY, keyString), new BasicDBObject(KEY, keyString).append(TYPE, TYPE_GRID_FS).append(FILE_NAME, fileName), true, false); } } }; } @Override synchronized public boolean check(byte[] key) { DBCursor cursor = collection.find(new BasicDBObject(KEY, adoptKey(key))); try { return !cursor.hasNext(); } finally { cursor.close(); } } @Override synchronized public boolean checkAndCreate(byte[] key) { try { collection.insert(new BasicDBObject(KEY, adoptKey(key))); } catch (MongoException e) { return false; } return true; } @Override synchronized public void delete(byte[] key) { collection.findAndRemove(new BasicDBObject(KEY, adoptKey(key))); } @Override synchronized public void drop() { collection.drop(); } @Override public void close() { } @Override public void flush() { db.cleanCursors(true); } private class Cursor implements ICursor { final DBCursor dbCursor; public Cursor(DBCursor dbCursor) { super(); this.dbCursor = dbCursor; } @Override public boolean hasNext() { return dbCursor.hasNext(); } @Override public byte[] next() { DBObject next = dbCursor.next(); if (next == null) { throw new IndexOutOfBoundsException(); } return (byte[])next.get(KEY); } @Override public InputStream openNextInputStream() { DBObject next = dbCursor.curr(); if (next == null) { throw new IndexOutOfBoundsException(); } return inputStreamFromDBObject(next); } @Override public void close() { dbCursor.close(); } } @Override public ICursor cursor(byte[] key) { DBCursor cursor = collection.find(new BasicDBObject(KEY, new BasicDBObject("$gte", adoptKey(key)))); cursor.sort(new BasicDBObject(KEY, 1)); return new Cursor(cursor); } }