Package com.mongodb.gridfs

Source Code of com.mongodb.gridfs.GridFSInputFile$MyOutputStream

/*
* 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.BasicDBObjectBuilder;
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;

/**
* <p>This class represents a GridFS file to be written to the database. Operations include:</p>
*
* <ul>
*     <li>Writing data obtained from an InputStream</li>
*     <li>Getting an OutputStream to stream the data out to</li>
* </ul>
*
* @mongodb.driver.manual core/gridfs/ GridFS
*/
public class GridFSInputFile extends GridFSFile {

    /**
     * Default constructor setting the GridFS file name and providing an input stream containing data to be written to the file.
     *
     * @param fs                   The GridFS connection handle.
     * @param in                   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( GridFS fs , InputStream in , String filename, boolean closeStreamOnPersist ) {
        _fs = fs;
        _in = in;
        _filename = filename;
        _closeStreamOnPersist = closeStreamOnPersist;

        _id = new ObjectId();
        _chunkSize = GridFS.DEFAULT_CHUNKSIZE;
        _uploadDate = new Date();
        try {
            _messageDigester = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("No MD5!");
        }
        _messageDigester.reset();
        _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 fs       The GridFS connection handle.
     * @param in       Stream used for reading data from.
     * @param filename Name of the file to be created.
     */
    protected GridFSInputFile( GridFS fs , InputStream in , String filename ) {
        this( fs, in, 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 fs       The GridFS connection handle.
     * @param filename Name of the file to be created.
     */
    protected GridFSInputFile( GridFS fs , String filename ) {
        this( fs , 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 fs The GridFS connection handle.
     */
    protected GridFSInputFile( GridFS fs ) {
        this( fs , null , null );
    }

    /**
     * Sets the ID of this GridFS file.
     *
     * @param id the file's ID.
     */
    public void setId(final Object id) {
        _id = id;
    }

    /**
     * Sets the file name on the GridFS entry.
     *
     * @param fn
     *            File name.
     */
    public void setFilename( String fn ) {
        _filename = fn;
    }

    /**
     * Sets the content type (MIME type) on the GridFS entry.
     *
     * @param ct
     *            Content type.
     */
    public void setContentType( String ct ) {
        _contentType = ct;
    }

    /**
     * Set the chunk size. This must be called before saving any data.
     *
     * @param chunkSize The size in bytes.
     */
    public void setChunkSize(long chunkSize) {
        if (_outputStream != null || _savedChunks)
            return;
        _chunkSize = chunkSize;
        _buffer = new byte[(int) _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( 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
     * @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
     */
    public int saveChunks( 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 ( _chunkSize != chunkSize ) {
            _chunkSize = chunkSize;
            _buffer = new byte[(int) _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 MyOutputStream();
        }
        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
     */
    private void _dumpBuffer( 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._chunkCollection.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 BasicDBObjectBuilder.start()
         .add("files_id", id)
         .add("n", currentChunkNumber)
         .add("data", writeBuffer).get();
    }

    /**
     * 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 = _in.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 ( _in != null && _closeStreamOnPersist )
                    _in.close();
            } catch (IOException e) {
                //ignore
            }
        }
    }

    private final InputStream _in;
    private boolean _closeStreamOnPersist;
    private boolean _savedChunks = false;
    private byte[] _buffer = null;
    private int _currentChunkNumber = 0;
    private int _currentBufferPosition = 0;
    private long _totalBytes = 0;
    private MessageDigest _messageDigester = null;
    private OutputStream _outputStream = null;

    /**
     * An output stream implementation that can be used to successively write to a GridFS file.
     */
    class MyOutputStream extends OutputStream {

        @Override
        public void write( int b ) throws IOException {
            byte[] byteArray = new byte[1];
            byteArray[0] = (byte) (b & 0xff);
            write( byteArray, 0, 1 );
        }

        @Override
        public void write( byte[] b , int off , 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();
        }
    }
}
TOP

Related Classes of com.mongodb.gridfs.GridFSInputFile$MyOutputStream

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.