com.mongodb.gridfs.GridFSInputFile Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mongo-java-driver Show documentation
Show all versions of mongo-java-driver Show documentation
The MongoDB Java Driver uber-artifact, containing mongodb-driver, mongodb-driver-core, and bson
The newest version!
/*
* Copyright (c) 2008-2014 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mongodb.gridfs;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.MongoException;
import com.mongodb.util.Util;
import org.bson.types.ObjectId;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
/**
* This class represents a GridFS file to be written to the database. Operations include:
*
*
* - Writing data obtained from an InputStream
* - Getting an OutputStream to stream the data out to
*
*
* @mongodb.driver.manual core/gridfs/ GridFS
*/
public class GridFSInputFile extends GridFSFile {
private final InputStream inputStream;
private final boolean closeStreamOnPersist;
private boolean savedChunks = false;
private byte[] buffer = null;
private int currentChunkNumber = 0;
private int currentBufferPosition = 0;
private long totalBytes = 0;
private OutputStream outputStream = null;
private MessageDigest messageDigester = null;
/**
* Default constructor setting the GridFS file name and providing an input stream containing data to be written to the file.
*
* @param gridFS The GridFS connection handle.
* @param inputStream Stream used for reading data from.
* @param filename Name of the file to be created.
* @param closeStreamOnPersist indicate the passed in input stream should be closed once the data chunk persisted
*/
protected GridFSInputFile(final GridFS gridFS, final InputStream inputStream, final String filename,
final boolean closeStreamOnPersist) {
this.fs = gridFS;
this.inputStream = inputStream;
this.filename = filename;
this.closeStreamOnPersist = closeStreamOnPersist;
this.id = new ObjectId();
this.chunkSize = GridFS.DEFAULT_CHUNKSIZE;
this.uploadDate = new Date();
try {
this.messageDigester = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("No MD5!");
}
this.messageDigester.reset();
this.buffer = new byte[(int) chunkSize];
}
/**
* Default constructor setting the GridFS file name and providing an input stream containing data to be written to the file.
*
* @param gridFS The GridFS connection handle.
* @param inputStream Stream used for reading data from.
* @param filename Name of the file to be created.
*/
protected GridFSInputFile(final GridFS gridFS, final InputStream inputStream, final String filename) {
this(gridFS, inputStream, filename, false);
}
/**
* Constructor that only provides a file name, but does not rely on the presence of an {@link java.io.InputStream}. An {@link
* java.io.OutputStream} can later be obtained for writing using the {@link #getOutputStream()} method.
*
* @param gridFS The GridFS connection handle.
* @param filename Name of the file to be created.
*/
protected GridFSInputFile(final GridFS gridFS, final String filename) {
this(gridFS, null, filename);
}
/**
* Minimal constructor that does not rely on the presence of an {@link java.io.InputStream}. An {@link java.io.OutputStream} can later
* be obtained for writing using the {@link #getOutputStream()} method.
*
* @param gridFS The GridFS connection handle.
*/
protected GridFSInputFile(final GridFS gridFS) {
this(gridFS, null, null);
}
/**
* Sets the ID of this GridFS file.
*
* @param id the file's ID.
*/
public void setId(final Object id) {
this.id = id;
}
/**
* Sets the file name on the GridFS entry.
*
* @param filename File name.
*/
public void setFilename(final String filename) {
this.filename = filename;
}
/**
* Sets the content type (MIME type) on the GridFS entry.
*
* @param contentType Content type.
*/
public void setContentType(final String contentType) {
this.contentType = contentType;
}
/**
* Set the chunk size. This must be called before saving any data.
*
* @param chunkSize The size in bytes.
*/
public void setChunkSize(final long chunkSize) {
if (outputStream != null || savedChunks) {
return;
}
this.chunkSize = chunkSize;
buffer = new byte[(int) this.chunkSize];
}
/**
* Calls {@link GridFSInputFile#save(long)} with the existing chunk size.
*
* @throws MongoException if there's a problem saving the file.
*/
@Override
public void save() {
save(chunkSize);
}
/**
* This method first calls saveChunks(long) if the file data has not been saved yet. Then it persists the file entry to GridFS.
*
* @param chunkSize Size of chunks for file in bytes.
* @throws MongoException if there's a problem saving the file.
*/
public void save(final long chunkSize) {
if (outputStream != null) {
throw new MongoException("cannot mix OutputStream and regular save()");
}
// note that chunkSize only changes chunkSize in case we actually save chunks
// otherwise there is a risk file and chunks are not compatible
if (!savedChunks) {
try {
saveChunks(chunkSize);
} catch (IOException ioe) {
throw new MongoException("couldn't save chunks", ioe);
}
}
super.save();
}
/**
* Saves all data into chunks from configured {@link java.io.InputStream} input stream to GridFS.
*
* @return Number of the next chunk.
* @throws IOException on problems reading the new entry's {@link java.io.InputStream}.
* @throws MongoException if there's a failure
* @see com.mongodb.gridfs.GridFSInputFile#saveChunks(long)
*/
public int saveChunks() throws IOException {
return saveChunks(chunkSize);
}
/**
* Saves all data into chunks from configured {@link java.io.InputStream} input stream to GridFS. A non-default chunk size can be
* specified. This method does NOT save the file object itself, one must call save() to do so.
*
* @param chunkSize Size of chunks for file in bytes.
* @return Number of the next chunk.
* @throws IOException on problems reading the new entry's {@link java.io.InputStream}.
* @throws MongoException if there's a failure
*/
public int saveChunks(final long chunkSize) throws IOException {
if (outputStream != null) {
throw new MongoException("Cannot mix OutputStream and regular save()");
}
if (savedChunks) {
throw new MongoException("Chunks already saved!");
}
if (chunkSize <= 0) {
throw new MongoException("chunkSize must be greater than zero");
}
if (this.chunkSize != chunkSize) {
this.chunkSize = chunkSize;
buffer = new byte[(int) this.chunkSize];
}
int bytesRead = 0;
while (bytesRead >= 0) {
currentBufferPosition = 0;
bytesRead = _readStream2Buffer();
dumpBuffer(true);
}
// only finish data, do not write file, in case one wants to change metadata
finishData();
return currentChunkNumber;
}
/**
* After retrieving this {@link java.io.OutputStream}, this object will be capable of accepting successively written data to the output
* stream. To completely persist this GridFS object, you must finally call the {@link java.io.OutputStream#close()} method on the output
* stream. Note that calling the save() and saveChunks() methods will throw Exceptions once you obtained the OutputStream.
*
* @return Writable stream object.
*/
public OutputStream getOutputStream() {
if (outputStream == null) {
outputStream = new GridFSOutputStream();
}
return outputStream;
}
/**
* Dumps a new chunk into the chunks collection. Depending on the flag, also partial buffers (at the end) are going to be written
* immediately.
*
* @param writePartial Write also partial buffers full.
* @throws MongoException if there's a failure
*/
private void dumpBuffer(final boolean writePartial) {
if ((currentBufferPosition < chunkSize) && !writePartial) {
// Bail out, chunk not complete yet
return;
}
if (currentBufferPosition == 0) {
// chunk is empty, may be last chunk
return;
}
byte[] writeBuffer = buffer;
if (currentBufferPosition != chunkSize) {
writeBuffer = new byte[currentBufferPosition];
System.arraycopy(buffer, 0, writeBuffer, 0, currentBufferPosition);
}
DBObject chunk = createChunk(id, currentChunkNumber, writeBuffer);
fs.getChunksCollection().save(chunk);
currentChunkNumber++;
totalBytes += writeBuffer.length;
messageDigester.update(writeBuffer);
currentBufferPosition = 0;
}
/**
* Creates a new chunk of this file. Can be over-ridden, if input files need to be split into chunks using a different mechanism.
*
* @param id the file ID
* @param currentChunkNumber the unique id for this chunk
* @param writeBuffer the byte array containing the data for this chunk
* @return a DBObject representing this chunk.
*/
protected DBObject createChunk(final Object id, final int currentChunkNumber, final byte[] writeBuffer) {
return new BasicDBObject("files_id", id)
.append("n", currentChunkNumber)
.append("data", writeBuffer);
}
/**
* Reads a buffer full from the {@link java.io.InputStream}.
*
* @return Number of bytes read from stream.
* @throws IOException if the reading from the stream fails.
*/
private int _readStream2Buffer() throws IOException {
int bytesRead = 0;
while (currentBufferPosition < chunkSize && bytesRead >= 0) {
bytesRead = inputStream.read(buffer, currentBufferPosition, (int) chunkSize - currentBufferPosition);
if (bytesRead > 0) {
currentBufferPosition += bytesRead;
} else if (bytesRead == 0) {
throw new RuntimeException("i'm doing something wrong");
}
}
return bytesRead;
}
/**
* Marks the data as fully written. This needs to be called before super.save()
*/
private void finishData() {
if (!savedChunks) {
md5 = Util.toHex(messageDigester.digest());
messageDigester = null;
length = totalBytes;
savedChunks = true;
try {
if (inputStream != null && closeStreamOnPersist) {
inputStream.close();
}
} catch (IOException e) {
//ignore
}
}
}
/**
* An output stream implementation that can be used to successively write to a GridFS file.
*/
private class GridFSOutputStream extends OutputStream {
@Override
public void write(final int b) throws IOException {
byte[] byteArray = new byte[1];
byteArray[0] = (byte) (b & 0xff);
write(byteArray, 0, 1);
}
@Override
public void write(final byte[] b, final int off, final int len) throws IOException {
int offset = off;
int length = len;
int toCopy = 0;
while (length > 0) {
toCopy = length;
if (toCopy > chunkSize - currentBufferPosition) {
toCopy = (int) chunkSize - currentBufferPosition;
}
System.arraycopy(b, offset, buffer, currentBufferPosition, toCopy);
currentBufferPosition += toCopy;
offset += toCopy;
length -= toCopy;
if (currentBufferPosition == chunkSize) {
dumpBuffer(false);
}
}
}
/**
* Processes/saves all data from {@link java.io.InputStream} and closes the potentially present {@link java.io.OutputStream}. The
* GridFS file will be persisted afterwards.
*/
@Override
public void close() {
// write last buffer if needed
dumpBuffer(true);
// finish stream
finishData();
// save file obj
GridFSInputFile.super.save();
}
}
}