View Javadoc
1   package de.dlr.shepard.data.file.services;
2   
3   import static com.mongodb.client.model.Filters.eq;
4   
5   import com.mongodb.client.MongoCollection;
6   import com.mongodb.client.MongoDatabase;
7   import com.mongodb.client.gridfs.GridFSBucket;
8   import com.mongodb.client.gridfs.GridFSBuckets;
9   import de.dlr.shepard.common.mongodb.NamedInputStream;
10  import de.dlr.shepard.common.util.DateHelper;
11  import de.dlr.shepard.common.util.UuidHelper;
12  import de.dlr.shepard.data.file.entities.ShepardFile;
13  import io.quarkus.logging.Log;
14  import jakarta.enterprise.context.RequestScoped;
15  import jakarta.inject.Inject;
16  import jakarta.inject.Named;
17  import jakarta.ws.rs.InternalServerErrorException;
18  import jakarta.ws.rs.NotFoundException;
19  import jakarta.xml.bind.DatatypeConverter;
20  import java.io.InputStream;
21  import java.security.DigestInputStream;
22  import java.security.MessageDigest;
23  import java.security.NoSuchAlgorithmException;
24  import org.bson.Document;
25  import org.bson.types.ObjectId;
26  
27  @RequestScoped
28  public class FileService {
29  
30    private static final int CHUNK_SIZE_BYTES = 1024 * 1024; // 1 MiB
31    private static final String ID_ATTR = "_id";
32    private static final String FILENAME_ATTR = "name";
33    private static final String FILEID_ATTR = "FileMongoId";
34    private static final String CREATEDAT_ATTR = "createdAt";
35    private static final String MD5_ATTR = "md5";
36  
37    @Inject
38    @Named("mongoDatabase")
39    MongoDatabase mongoDatabase;
40  
41    @Inject
42    UuidHelper uuidHelper;
43  
44    @Inject
45    DateHelper dateHelper;
46  
47    public String createFileContainer() {
48      String oid = "FileContainer" + uuidHelper.getUUID().toString();
49      mongoDatabase.createCollection(oid);
50      return oid;
51    }
52  
53    /**
54     * Creates a new file in file container
55     *
56     * @param mongoId
57     * @param fileName
58     * @param inputStream
59     * @return ShepardFile
60     */
61    public ShepardFile createFile(String mongoId, String fileName, InputStream inputStream) {
62      MongoCollection<Document> collection;
63      try {
64        collection = mongoDatabase.getCollection(mongoId);
65      } catch (IllegalArgumentException e) {
66        throw new MongoContainerNotFound(mongoId);
67      }
68  
69      MessageDigest md;
70      try {
71        md = MessageDigest.getInstance("MD5");
72      } catch (NoSuchAlgorithmException e) {
73        String errorMsg = "No Such Algorithm while uploading file";
74        Log.error(errorMsg);
75        throw new InternalServerErrorException(errorMsg);
76      }
77  
78      DigestInputStream dis = new DigestInputStream(inputStream, md);
79      String fileMongoId = createBucket()
80        .withChunkSizeBytes(CHUNK_SIZE_BYTES)
81        .uploadFromStream(fileName, dis)
82        .toHexString();
83      var file = new ShepardFile(dateHelper.getDate(), fileName, DatatypeConverter.printHexBinary(md.digest()));
84      var doc = toDocument(file).append(FILEID_ATTR, fileMongoId);
85      collection.insertOne(doc);
86      file.setOid(doc.getObjectId(ID_ATTR).toHexString());
87      return file;
88    }
89  
90    /**
91     * Returns the payload as an InputStream for a given file.
92     *
93     * @param containerId
94     * @param fileOid
95     * @return NamedInputStream
96     * @throws NotFoundException if container could not be found by MongoId or document could be found by Oid
97     */
98    public NamedInputStream getPayload(String containerId, String fileOid) {
99      MongoCollection<Document> collection;
100     try {
101       collection = mongoDatabase.getCollection(containerId);
102     } catch (IllegalArgumentException e) {
103       throw new MongoContainerNotFound(containerId);
104     }
105     var oid = new ObjectId(fileOid);
106     var payloadDocument = collection.find(eq(ID_ATTR, oid)).first();
107     if (payloadDocument == null) {
108       String errorMsg = "Could not find document with oid: %s".formatted(fileOid);
109       Log.error(errorMsg);
110       throw new NotFoundException(errorMsg);
111     }
112     var fileId = new ObjectId(payloadDocument.getString(FILEID_ATTR));
113     var filename = payloadDocument.getString(FILENAME_ATTR);
114     var gridBucket = createBucket();
115     var gridFsFile = gridBucket.find(eq(ID_ATTR, fileId)).first();
116     if (gridFsFile == null) throw new NotFoundException("File %s does not exist in MongoDB bucket!".formatted(fileOid));
117     var inputStream = gridBucket.openDownloadStream(fileId);
118 
119     return new NamedInputStream(fileOid, inputStream, filename, gridFsFile.getLength());
120   }
121 
122   /**
123    * Returns a ShepardFile for a given container and oid
124    *
125    * @param containerId
126    * @param fileOid
127    * @return ShepardFile
128    * @throws NotFoundException if container could not be found by MongoId or file could be found by Oid
129    */
130   public ShepardFile getFile(String containerId, String fileOid) {
131     MongoCollection<Document> collection;
132     try {
133       collection = mongoDatabase.getCollection(containerId);
134     } catch (IllegalArgumentException e) {
135       throw new MongoContainerNotFound(containerId);
136     }
137     var doc = collection.find(eq(ID_ATTR, new ObjectId(fileOid))).first();
138     if (doc == null) {
139       String errorMsg = "Could not find file with oid: %s".formatted(fileOid);
140       Log.error(errorMsg);
141       throw new NotFoundException(errorMsg);
142     }
143     return toShepardFile(doc);
144   }
145 
146   /**
147    * @param mongoId
148    * @throws NotFoundException if deleting container fails
149    */
150   public void deleteFileContainer(String mongoId) {
151     MongoCollection<Document> toDelete;
152     try {
153       toDelete = mongoDatabase.getCollection(mongoId);
154     } catch (IllegalArgumentException e) {
155       String errorMsg = "Could not delete container with mongoid: %s".formatted(mongoId);
156       Log.error(errorMsg);
157       throw new NotFoundException(errorMsg);
158     }
159     GridFSBucket gridBucket = createBucket();
160     for (Document doc : toDelete.find()) {
161       gridBucket.delete(new ObjectId(doc.getString(FILEID_ATTR)));
162     }
163     toDelete.drop();
164   }
165 
166   public void deleteFile(String mongoId, String fileOid) {
167     MongoCollection<Document> collection;
168     try {
169       collection = mongoDatabase.getCollection(mongoId);
170     } catch (IllegalArgumentException e) {
171       throw new MongoContainerNotFound(mongoId);
172     }
173     var doc = collection.findOneAndDelete(eq(ID_ATTR, new ObjectId(fileOid)));
174     if (doc == null) {
175       String errorMsg = "Could not find and delete file with oid: %s".formatted(fileOid);
176       Log.error(errorMsg);
177       throw new NotFoundException(errorMsg);
178     }
179     var gridBucket = createBucket();
180     gridBucket.delete(new ObjectId(doc.getString(FILEID_ATTR)));
181   }
182 
183   private static ShepardFile toShepardFile(Document doc) {
184     var file = new ShepardFile(
185       doc.getObjectId(ID_ATTR).toHexString(),
186       doc.getDate(CREATEDAT_ATTR),
187       doc.getString(FILENAME_ATTR),
188       doc.getString(MD5_ATTR)
189     );
190     return file;
191   }
192 
193   private static Document toDocument(ShepardFile file) {
194     var doc = new Document()
195       .append(CREATEDAT_ATTR, file.getCreatedAt())
196       .append(FILENAME_ATTR, file.getFilename())
197       .append(MD5_ATTR, file.getMd5());
198     if (file.getOid() != null) doc.append(ID_ATTR, new ObjectId(file.getOid()));
199     return doc;
200   }
201 
202   private GridFSBucket createBucket() {
203     return GridFSBuckets.create(mongoDatabase);
204   }
205 }