/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.ignite.internal.processors.igfs; import org.apache.ignite.events.IgfsEvent; import org.apache.ignite.igfs.IgfsOutputStream; import org.apache.ignite.igfs.IgfsPath; import org.apache.ignite.internal.managers.eventstorage.GridEventStorageManager; import org.apache.ignite.internal.util.typedef.internal.A; import org.apache.ignite.internal.util.typedef.internal.S; import org.jetbrains.annotations.Nullable; import java.io.DataInput; import java.io.IOException; import java.nio.ByteBuffer; import static org.apache.ignite.events.EventType.EVT_IGFS_FILE_CLOSED_WRITE; /** * Output stream to store data into grid cache with separate blocks. */ abstract class IgfsAbstractOutputStream extends IgfsOutputStream { /** IGFS context. */ protected final IgfsContext igfsCtx; /** Path to file. */ protected final IgfsPath path; /** Buffer size. */ protected final int bufSize; /** File worker batch. */ protected final IgfsFileWorkerBatch batch; /** Mutex for synchronization. */ protected final Object mux = new Object(); /** Flag for this stream open/closed state. */ protected boolean closed; /** Local buffer to store stream data as consistent block. */ protected ByteBuffer buf; /** Bytes written. */ protected long bytes; /** Time consumed by write operations. */ protected long time; /** * Constructs file output stream. * * @param igfsCtx IGFS context. * @param path Path to stored file. * @param bufSize The size of the buffer to be used. * @param batch Optional secondary file system batch. */ IgfsAbstractOutputStream(IgfsContext igfsCtx, IgfsPath path, int bufSize, @Nullable IgfsFileWorkerBatch batch) { synchronized (mux) { this.path = path; this.bufSize = optimizeBufferSize(bufSize); this.igfsCtx = igfsCtx; this.batch = batch; } igfsCtx.metrics().incrementFilesOpenedForWrite(); } /** * Optimize buffer size. * * @param bufSize Original byffer size. * @return Optimized buffer size. */ protected abstract int optimizeBufferSize(int bufSize); /** {@inheritDoc} */ @Override public void write(int b) throws IOException { synchronized (mux) { checkClosed(null, 0); b &= 0xFF; long startTime = System.nanoTime(); if (buf == null) buf = allocateNewBuffer(); buf.put((byte)b); sendBufferIfFull(); time += System.nanoTime() - startTime; } } /** {@inheritDoc} */ @SuppressWarnings("NullableProblems") @Override public void write(byte[] b, int off, int len) throws IOException { A.notNull(b, "b"); if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException("Invalid bounds [data.length=" + b.length + ", offset=" + off + ", length=" + len + ']'); } synchronized (mux) { checkClosed(null, 0); // Check if there is anything to write. if (len == 0) return; long startTime = System.nanoTime(); if (buf == null) { if (len >= bufSize) { // Send data right away. ByteBuffer tmpBuf = ByteBuffer.wrap(b, off, len); send(tmpBuf, tmpBuf.remaining()); } else { buf = allocateNewBuffer(); buf.put(b, off, len); } } else { // Re-allocate buffer if needed. if (buf.remaining() < len) buf = ByteBuffer.allocate(buf.position() + len).put((ByteBuffer)buf.flip()); buf.put(b, off, len); sendBufferIfFull(); } time += System.nanoTime() - startTime; } } /** {@inheritDoc} */ @Override public void transferFrom(DataInput in, int len) throws IOException { synchronized (mux) { checkClosed(in, len); long startTime = System.nanoTime(); // Clean-up local buffer before streaming. sendBufferIfNotEmpty(); // Perform transfer. send(in, len); time += System.nanoTime() - startTime; } } /** * Validate this stream is open. * * @param in Data input. * @param len Data len in bytes. * @throws IOException If this stream is closed. */ protected void checkClosed(@Nullable DataInput in, int len) throws IOException { assert Thread.holdsLock(mux); if (closed) { // Must read data from stream before throwing exception. if (in != null) in.skipBytes(len); throw new IOException("Stream has been closed: " + this); } } /** * Send local buffer if it full. * * @throws IOException If failed. */ private void sendBufferIfFull() throws IOException { if (buf.position() >= bufSize) sendBuffer(); } /** * Send local buffer if at least something is stored there. * * @throws IOException If failed. */ void sendBufferIfNotEmpty() throws IOException { if (buf != null && buf.position() > 0) sendBuffer(); } /** * Send all local-buffered data to server. * * @throws IOException In case of IO exception. */ private void sendBuffer() throws IOException { buf.flip(); send(buf, buf.remaining()); buf = null; } /** * Store data block. * * @param data Block. * @param writeLen Write length. * @throws IOException If failed. */ protected abstract void send(Object data, int writeLen) throws IOException; /** * Allocate new buffer. * * @return New buffer. */ private ByteBuffer allocateNewBuffer() { return ByteBuffer.allocate(bufSize); } /** * Updates IGFS metrics when the stream is closed. */ protected void updateMetricsOnClose() { IgfsLocalMetrics metrics = igfsCtx.metrics(); metrics.addWrittenBytesTime(bytes, time); metrics.decrementFilesOpenedForWrite(); GridEventStorageManager evts = igfsCtx.kernalContext().event(); if (evts.isRecordable(EVT_IGFS_FILE_CLOSED_WRITE)) evts.record(new IgfsEvent(path, igfsCtx.localNode(), EVT_IGFS_FILE_CLOSED_WRITE, bytes)); } /** {@inheritDoc} */ @Override public String toString() { return S.toString(IgfsAbstractOutputStream.class, this); } }