All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opendaylight.controller.cluster.messaging.AssembledMessageState Maven / Gradle / Ivy

There is a newer version: 10.0.5
Show newest version
/*
 * Copyright (c) 2017 Inocybe Technologies and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.controller.cluster.messaging;

import com.google.common.base.Preconditions;
import com.google.common.io.ByteSource;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.util.Arrays;
import org.opendaylight.controller.cluster.io.FileBackedOutputStream;
import org.opendaylight.controller.cluster.io.FileBackedOutputStreamFactory;
import org.opendaylight.yangtools.concepts.Identifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Maintains the state of an assembled message. This class is NOT thread-safe.
 *
 * @author Thomas Pantelis
 */
public class AssembledMessageState implements AutoCloseable {
    private static final Logger LOG = LoggerFactory.getLogger(AssembledMessageState.class);

    private final int totalSlices;
    private final BufferedOutputStream bufferedStream;
    private final FileBackedOutputStream fileBackedStream;
    private final Identifier identifier;
    private final String logContext;

    private int lastSliceIndexReceived = SlicedMessageState.FIRST_SLICE_INDEX - 1;
    private int lastSliceHashCodeReceived = SlicedMessageState.INITIAL_SLICE_HASH_CODE;
    private boolean sealed = false;
    private boolean closed = false;
    private long assembledSize;

    /**
     * Constructor.
     *
     * @param identifier the identifier for this instance
     * @param totalSlices the total number of slices to expect
     * @param fileBackedStreamFactory factory for creating the FileBackedOutputStream instance used for streaming
     * @param logContext the context for log messages
     */
    public AssembledMessageState(final Identifier identifier, final int totalSlices,
            final FileBackedOutputStreamFactory fileBackedStreamFactory, final String logContext) {
        this.identifier = identifier;
        this.totalSlices = totalSlices;
        this.logContext = logContext;

        fileBackedStream = fileBackedStreamFactory.newInstance();
        bufferedStream = new BufferedOutputStream(fileBackedStream);
    }

    /**
     * Returns the identifier of this instance.
     *
     * @return the identifier
     */
    public Identifier getIdentifier() {
        return identifier;
    }

    /**
     * Adds a slice to the assembled stream.
     *
     * @param sliceIndex the index of the slice
     * @param data the sliced data
     * @param lastSliceHashCode the hash code of the last slice sent
     * @return true if this is the last slice received, false otherwise
     * @throws MessageSliceException
     *         
    *
  • if the slice index is invalid
  • *
  • if the last slice hash code is invalid
  • *
  • if an error occurs writing the data to the stream
  • *
* In addition, this instance is automatically closed and can no longer be used. * @throws AssemblerSealedException if this instance is already sealed (ie has received all the slices) * @throws AssemblerClosedException if this instance is already closed */ public boolean addSlice(final int sliceIndex, final byte[] data, final int lastSliceHashCode) throws MessageSliceException { if (LOG.isDebugEnabled()) { LOG.debug("{}: addSlice: identifier: {}, sliceIndex: {}, lastSliceIndex: {}, assembledSize: {}, " + "sliceHashCode: {}, lastSliceHashCode: {}", logContext, identifier, sliceIndex, lastSliceIndexReceived, assembledSize, lastSliceHashCode, lastSliceHashCodeReceived); } try { validateSlice(sliceIndex, lastSliceHashCode); assembledSize += data.length; lastSliceIndexReceived = sliceIndex; lastSliceHashCodeReceived = Arrays.hashCode(data); bufferedStream.write(data); sealed = sliceIndex == totalSlices; if (sealed) { bufferedStream.close(); } } catch (IOException e) { close(); throw new MessageSliceException(String.format("Error writing data for slice %d of message %s", sliceIndex, identifier), e); } return sealed; } /** * Returns the assembled bytes as a ByteSource. This method must only be called after this instance is sealed. * * @return a ByteSource containing the assembled bytes * @throws IOException if an error occurs obtaining the assembled bytes * @throws IllegalStateException is this instance is not sealed */ public ByteSource getAssembledBytes() throws IOException { Preconditions.checkState(sealed, "Last slice not received yet"); return fileBackedStream.asByteSource(); } private void validateSlice(final int sliceIndex, final int lastSliceHashCode) throws MessageSliceException { if (closed) { throw new AssemblerClosedException(identifier); } if (sealed) { throw new AssemblerSealedException(String.format( "Received slice index for message %s but all %d expected slices have already already received.", identifier, totalSlices)); } if (lastSliceIndexReceived + 1 != sliceIndex) { close(); throw new MessageSliceException(String.format("Expected sliceIndex %d but got %d for message %s", lastSliceIndexReceived + 1, sliceIndex, identifier), true); } if (lastSliceHashCode != lastSliceHashCodeReceived) { close(); throw new MessageSliceException(String.format("The hash code of the recorded last slice (%d) does not " + "match the senders last hash code (%d) for message %s", lastSliceHashCodeReceived, lastSliceHashCode, identifier), true); } } @Override public void close() { if (closed) { return; } closed = true; if (!sealed) { try { bufferedStream.close(); } catch (IOException e) { LOG.debug("{}: Error closing output stream", logContext, e); } } fileBackedStream.cleanup(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy