co.cask.cdap.data.file.PartitionedFileWriter.java Source code

Java tutorial

Introduction

Here is the source code for co.cask.cdap.data.file.PartitionedFileWriter.java

Source

/*
 * Copyright  2014-2015 Cask Data, 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 co.cask.cdap.data.file;

import com.google.common.base.Objects;
import com.google.common.base.Throwables;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import com.google.common.collect.PeekingIterator;
import com.google.common.io.Closeables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import javax.annotation.concurrent.NotThreadSafe;

/**
 * An abstract base class for implementation partitioned {@link FileWriter}.
 *
 * @param <T> Type of event.
 * @param <P> Type of partition.
 */
@NotThreadSafe
public abstract class PartitionedFileWriter<T, P> implements FileWriter<T> {

    private static final Logger LOG = LoggerFactory.getLogger(PartitionedFileWriter.class);

    private final PartitionedFileWriterFactory<T, P> fileWriterFactory;
    private final Map<P, FileWriter<T>> writers;
    private P currentPartition;
    private FileWriter<T> currentWriter;
    private boolean closed;

    /**
     * Constructs with the given file writer factory.
     */
    protected PartitionedFileWriter(PartitionedFileWriterFactory<T, P> fileWriterFactory) {
        this.fileWriterFactory = fileWriterFactory;
        this.writers = Maps.newHashMap();
    }

    @Override
    public void append(T event) throws IOException {
        if (closed) {
            throw new IOException("Attempts to write to a closed FileWriter.");
        }

        try {
            getWriter(event).append(event);
        } catch (Throwable t) {
            LOG.error("Exception on append.", t);
            Closeables.closeQuietly(this);
            Throwables.propagateIfInstanceOf(t, IOException.class);
            throw Throwables.propagate(t);
        }
    }

    @Override
    public void appendAll(final Iterator<? extends T> events) throws IOException {
        if (closed) {
            throw new IOException("Attempts to write to a closed FileWriter.");
        }

        PeekingIterator<T> iterator = Iterators.peekingIterator(events);
        while (iterator.hasNext()) {
            getWriter(iterator.peek()).appendAll(new AppendIterator(iterator));
        }
    }

    @Override
    public void flush() throws IOException {
        // Special case to avoid looping the map values
        if (writers.size() == 1) {
            currentWriter.flush();
        } else if (!writers.isEmpty()) {
            IOException flushException = null;
            for (FileWriter<T> writer : writers.values()) {
                try {
                    writer.flush();
                } catch (IOException e) {
                    flushException = e;
                }
            }

            if (flushException != null) {
                throw flushException;
            }
        }
    }

    @Override
    public void close() throws IOException {
        try {
            IOException closeException = null;
            for (FileWriter<T> writer : writers.values()) {
                try {
                    writer.close();
                } catch (IOException e) {
                    // Catch the exception and throw after the loop as we want to close all writers.
                    closeException = e;
                }
            }
            if (closeException != null) {
                throw closeException;
            }
        } finally {
            closed = true;
        }
    }

    /**
     * Gets a {@link FileWriter} for the given event.
     */
    private FileWriter<T> getWriter(T event) throws IOException {
        P partition = getPartition(event);

        // If the partition changed, get the file writer for the new partition.
        if (!Objects.equal(currentPartition, partition)) {
            // Notify that partition changed, so that children class can take action if necessary.
            partitionChanged(currentPartition, partition);
            currentWriter = writers.get(partition);
            if (currentWriter == null) {
                currentWriter = fileWriterFactory.create(partition);
                writers.put(partition, currentWriter);
            }
            currentPartition = partition;
        }

        return currentWriter;
    }

    /**
     * Closes the {@link FileWriter} for the given partition.
     */
    protected final void closePartitionWriter(P partition) throws IOException {
        FileWriter<T> writer = writers.remove(partition);
        if (writer != null) {
            writer.close();
        }
    }

    /**
     * Invoked when partition changed (different from the last event get appended).
     */
    protected void partitionChanged(P oldPartition, P newPartition) throws IOException {
        // No-op by default.
    }

    /**
     * Returns the partition ID for the given event.
     *
     * @param event The event for computing a partition ID.
     * @return A long representing the partition ID.
     */
    protected abstract P getPartition(T event);

    /**
     *
     * @param <T> Type of event that can be appended to the {@link FileWriter} created by this factory.
     * @param <P> Type of partition.
     */
    protected interface PartitionedFileWriterFactory<T, P> {

        /**
         * Creates a {@link FileWriter} for writing event of type {@code T} for the given partition.
         *
         * @param partition Partition information.
         * @return A {@link FileWriter}.
         * @throws java.io.IOException If creation failed.
         */
        FileWriter<T> create(P partition) throws IOException;
    }

    /**
     * An {@link Iterator} to support the {@link PartitionedFileWriter#appendAll(Iterator)} operation.
     * It will write as many events as possible as long as the event partition stays the same.
     */
    private final class AppendIterator extends AbstractIterator<T> {

        private final PeekingIterator<? extends T> events;

        AppendIterator(PeekingIterator<? extends T> events) {
            this.events = events;
        }

        @Override
        protected T computeNext() {
            if (!events.hasNext()) {
                return endOfData();
            }

            T event = events.peek();
            P partition = getPartition(event);
            if (Objects.equal(currentPartition, partition)) {
                events.next();
                return event;
            }
            return endOfData();
        }
    }
}