/* * Copyright (c) 2017 OBiBa. All rights reserved. * * This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.obiba.magma.datasource.mongodb; import java.util.Date; import java.util.List; import javax.annotation.Nullable; import javax.validation.constraints.NotNull; import org.bson.BSONObject; import org.bson.types.ObjectId; import org.obiba.magma.MagmaRuntimeException; import org.obiba.magma.Value; import org.obiba.magma.ValueTableWriter; import org.obiba.magma.Variable; import org.obiba.magma.VariableEntity; import org.obiba.magma.datasource.mongodb.converter.ValueConverter; import org.obiba.magma.datasource.mongodb.converter.VariableConverter; import org.obiba.magma.type.BinaryType; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObjectBuilder; import com.mongodb.BulkWriteOperation; import com.mongodb.BulkWriteResult; import com.mongodb.DBCollection; import com.mongodb.DBCursor; import com.mongodb.DBObject; import com.mongodb.gridfs.GridFS; import com.mongodb.gridfs.GridFSInputFile; class MongoDBValueTableWriter implements ValueTableWriter { static final String GRID_FILE_ID = "_grid_file_id"; static final String GRID_FILE_SIZE = "size"; static final String GRID_FILE_MD5 = "md5"; private final MongoDBValueTable table; private final List<DBObject> batch = Lists.newArrayList(); MongoDBValueTableWriter(@NotNull MongoDBValueTable table) { this.table = table; } @Override public VariableWriter writeVariables() { return new MongoDBVariableWriter(); } @NotNull @Override public ValueSetWriter writeValueSet(@NotNull VariableEntity entity) { return new MongoDBValueSetWriter(entity); } @Override public void close() { List<DBObject> toSave = null; synchronized(table) { if(!batch.isEmpty()) { toSave = ImmutableList.copyOf(batch); batch.clear(); } } if(toSave != null) insertOrReplaceBatch(toSave); updateLastUpdate(); } private void updateLastUpdate() { table.setLastUpdate(new Date()); } private void insertOrReplaceBatch(List<DBObject> toSave) { BulkWriteOperation bulkWriteOperation = table.getValueSetCollection().initializeOrderedBulkOperation(); for (DBObject obj: toSave) { bulkWriteOperation.find(BasicDBObjectBuilder.start("_id", obj.get("_id")).get())// .upsert()// .replaceOne(obj); } bulkWriteOperation.execute(); } private class MongoDBValueSetWriter implements ValueTableWriter.ValueSetWriter { private final VariableEntity entity; private DBObject valueSetObject; private boolean removed = false; private MongoDBValueSetWriter(VariableEntity entity) { this.entity = entity; } private DBObject getValueSetObject() { if(valueSetObject == null) { DBObject template = BasicDBObjectBuilder.start("_id", entity.getIdentifier()).get(); valueSetObject = table.getValueSetCollection().findOne(template); if(valueSetObject == null) { valueSetObject = template; valueSetObject.put(MongoDBDatasource.TIMESTAMPS_FIELD, MongoDBDatasource.createTimestampsObject()); } } return valueSetObject; } @Override public void writeValue(@NotNull Variable variable, Value value) { removed = false; MongoDBVariable varObj = (MongoDBVariable) table.getVariable(variable.getName()); String field = varObj.getId(); if(BinaryType.get().equals(value.getValueType())) { DBObject fileMetadata = getValueSetObject().containsField(field) ? updateBinary(variable, value, field) : createBinary(variable, value); getValueSetObject().put(field, fileMetadata); } else { getValueSetObject().put(field, ValueConverter.marshall(variable, value)); } } @Override public void remove() { removed = true; // remove files if any for(Variable variable : table.getVariables()) { if(BinaryType.get().equals(variable.getValueType())) { String field = ((MongoDBVariable) variable).getId(); removeFile(variable, field); } } // then remove value set document table.getValueSetCollection().remove(BasicDBObjectBuilder.start("_id", entity.getIdentifier()).get()); } @Nullable private DBObject updateBinary(Variable variable, Value value, String field) { removeFile(variable, field); return value.isNull() ? null : createBinary(variable, value); } @SuppressWarnings("unchecked") private void removeFile(Variable variable, String field) { BSONObject binaryValueMetaData = (BSONObject) getValueSetObject().get(field); if(binaryValueMetaData != null) { GridFS gridFS = table.getMongoDBFactory().getGridFS(); if(variable.isRepeatable()) { for(BSONObject obj : (Iterable<BSONObject>) binaryValueMetaData) { gridFS.remove(new ObjectId((String) obj.get(GRID_FILE_ID))); } } else { gridFS.remove(new ObjectId((String) binaryValueMetaData.get(GRID_FILE_ID))); } } } @Nullable private DBObject createBinary(Variable variable, Value value) { if(value.isNull()) { return null; } if(variable.isRepeatable()) { int occurrence = 0; BasicDBList metadata = new BasicDBList(); for(Value occurrenceValue : value.asSequence().getValues()) { metadata.add(createFile(variable, occurrenceValue, occurrence++)); } return metadata; } return createFile(variable, value, null); } private DBObject createFile(Variable variable, Value value, Integer occurrence) { if(value.isNull()) { return getBinaryValueMetadata(null, occurrence); } BasicDBObjectBuilder metaDataBuilder = BasicDBObjectBuilder.start() // .add("datasource", table.getDatasource().getName()) // .add("table", table.getName()) // .add("variable", variable.getName()) // .add("entity", entity.getIdentifier()); if(occurrence != null) metaDataBuilder.add("occurrence", occurrence); GridFSInputFile gridFSFile = table.getMongoDBFactory().getGridFS().createFile((byte[]) value.getValue()); gridFSFile.setMetaData(metaDataBuilder.get()); gridFSFile.save(); return getBinaryValueMetadata(gridFSFile, occurrence); } @Override public void close() { if(!removed) { BSONObject timestamps = (BSONObject) getValueSetObject().get(MongoDBDatasource.TIMESTAMPS_FIELD); timestamps.put(MongoDBDatasource.TIMESTAMPS_UPDATED_FIELD, new Date()); int batchSize = ((MongoDBDatasource)table.getDatasource()).getBatchSize(); if(batchSize == 1) { table.getValueSetCollection().save(getValueSetObject()); } else { List<DBObject> toSave = null; DBObject valueSet = getValueSetObject(); synchronized(table) { batch.add(valueSet); if(batch.size() >= batchSize) { toSave = ImmutableList.copyOf(batch); batch.clear(); } } if(toSave != null) { insertOrReplaceBatch(toSave); } } } updateLastUpdate(); } private DBObject getBinaryValueMetadata(@Nullable GridFSInputFile gridFSFile, Integer occurrence) { BasicDBObjectBuilder builder = BasicDBObjectBuilder.start(); if(gridFSFile != null) { builder.add(GRID_FILE_ID, gridFSFile.getId().toString()) // .add(GRID_FILE_SIZE, gridFSFile.getLength()) // .add(GRID_FILE_MD5, gridFSFile.getMD5()); } if(occurrence != null) builder.add("occurrence", occurrence); return builder.get(); } } private class MongoDBVariableWriter implements ValueTableWriter.VariableWriter { @Override public void writeVariable(@NotNull Variable variable) { DBObject existingDbObject = table.findVariable(variable.getName()); if(existingDbObject == null) { table.addVariableValueSource(new MongoDBVariableValueSource(table, variable.getName())); } // insert or update DBObject varObject = VariableConverter.marshall(variable); if(existingDbObject != null) { varObject.put("_id", existingDbObject.get("_id")); } table.getVariablesCollection().save(varObject); updateLastUpdate(); } @Override public void removeVariable(@NotNull Variable variable) { DBObject varObj = table.findVariable(variable.getName()); if(varObj == null) return; // remove from the variable collection table.removeVariableValueSource(variable.getName()); table.getVariablesCollection().remove(varObj); // remove associated values from the value set collection removeVariableValues((MongoDBVariable) variable); if(table.getVariableCount() == 0) { table.getValueSetCollection().remove(BasicDBObjectBuilder.start().get()); } updateLastUpdate(); } private void removeVariableValues(@NotNull MongoDBVariable variable) { DBCollection valueSetCollection = table.getValueSetCollection(); DBCursor cursor = valueSetCollection.find(); String field = variable.getId(); while(cursor.hasNext()) { DBObject valueSetObject = cursor.next(); if(variable.getValueType().equals(BinaryType.get())) { removeBinaryFiles(variable, field, valueSetObject); } valueSetObject.removeField(field); BSONObject timestamps = (BSONObject) valueSetObject.get(MongoDBDatasource.TIMESTAMPS_FIELD); timestamps.put(MongoDBDatasource.TIMESTAMPS_UPDATED_FIELD, new Date()); valueSetCollection.save(valueSetObject); } } @SuppressWarnings("unchecked") private void removeBinaryFiles(Variable variable, String field, BSONObject valueSetObject) { BSONObject fileMetadata = (BSONObject) valueSetObject.get(field); if(fileMetadata == null) return; if(variable.isRepeatable()) { for(BSONObject occurrenceObj : (Iterable<BSONObject>) fileMetadata) { removeFile(occurrenceObj); } } else { removeFile(fileMetadata); } } private void removeFile(BSONObject fileMetadata) { if(fileMetadata.containsField(GRID_FILE_ID)) { table.getMongoDBFactory().getGridFS().remove(new ObjectId((String) fileMetadata.get(GRID_FILE_ID))); } } @Override public void close() { } } }