lcn.module.batch.core.item.file.PartitionFlatFileItemWriter.java Source code

Java tutorial

Introduction

Here is the source code for lcn.module.batch.core.item.file.PartitionFlatFileItemWriter.java

Source

/*
 * Copyright 2006-2007 the original author or authors.
 *
 * 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 lcn.module.batch.core.item.file;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Writer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.charset.UnsupportedCharsetException;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.WriteFailedException;
import org.springframework.batch.item.WriterNotOpenException;
import org.springframework.batch.item.file.FlatFileFooterCallback;
import org.springframework.batch.item.file.FlatFileHeaderCallback;
import org.springframework.batch.item.file.ResourceAwareItemWriterItemStream;
import org.springframework.batch.item.file.transform.LineAggregator;
import org.springframework.batch.item.util.ExecutionContextUserSupport;
import org.springframework.batch.item.util.FileUtils;
import org.springframework.batch.support.transaction.TransactionAwareBufferedWriter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
 * Partition    ?  ? target?? Write. writer  scope=step ?
 *  .
 * 
 * @author 
 * @since 2012. 07.30
 * @version 1.0
 * @see
 * 
 *      <pre>
 * << ?(Modification Information) >>
 *   
 *   ?               ?               
 *  -------      --------     ---------------------------
 *  2012. 07.30       ?
 * 
 * </pre>
 */

public class PartitionFlatFileItemWriter<T> extends ExecutionContextUserSupport
        implements ResourceAwareItemWriterItemStream<T>, InitializingBean {

    private static final boolean DEFAULT_TRANSACTIONAL = true;

    protected static final Log logger = LogFactory.getLog(PartitionFlatFileItemWriter.class);

    private static final String DEFAULT_LINE_SEPARATOR = System.getProperty("line.separator");

    private static final String WRITTEN_STATISTICS_NAME = "written";

    private static final String RESTART_DATA_NAME = "current.count";

    private Resource resource;

    private OutputState state = null;

    private LineAggregator<T> lineAggregator;

    private boolean saveState = true;

    // ? ?? ??  ? ?  
    private boolean shouldDeleteIfExists = true;

    // ??  ?  ?  ? ?  
    private boolean shouldDeleteIfEmpty = false;

    private String encoding = OutputState.DEFAULT_CHARSET;

    private FlatFileHeaderCallback headerCallback;

    private FlatFileFooterCallback footerCallback;

    private String lineSeparator = DEFAULT_LINE_SEPARATOR;

    private boolean transactional = DEFAULT_TRANSACTIONAL;

    private boolean append = false;

    // fileCount : file ?  ? ? Counting
    // 0 ? ??? Closing? 
    private int fileCount = 0;

    // fileOpenTime : file?  open ? ? 
    private long fileOpenTime = 0;

    // fileOpenTime : file?  close ? ? 
    private long fileCloseTime = 0;

    public PartitionFlatFileItemWriter() {
        setName(ClassUtils.getShortName(PartitionFlatFileItemWriter.class));
    }

    /**
     * ?? ? 
     */
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(lineAggregator, "A LineAggregator must be provided.");

        if (append) {
            shouldDeleteIfExists = false;
        }
    }

    /**
     * lineSeparator 
     * 
     * @param lineSeparator
     */
    public void setLineSeparator(String lineSeparator) {
        this.lineSeparator = lineSeparator;
    }

    /**
     * lineAggregator 
     * 
     * @param lineAggregator
     */
    public void setLineAggregator(LineAggregator<T> lineAggregator) {
        this.lineAggregator = lineAggregator;
    }

    /**
     * fileCount 
     * 
     * @param fileCount
     */
    public void setFileCount(int fileCount) {
        this.fileCount = fileCount;
    }

    /**
     * resource 
     */
    public void setResource(Resource resource) {
        this.resource = resource;
    }

    /**
     * encoding 
     */
    public void setEncoding(String newEncoding) {
        this.encoding = newEncoding;
    }

    /**
     * shouldDeleteIfExists 
     */
    public void setShouldDeleteIfExists(boolean shouldDeleteIfExists) {
        this.shouldDeleteIfExists = shouldDeleteIfExists;
    }

    /**
     *
     */
    public void setAppendAllowed(boolean append) {
        this.append = append;
        this.shouldDeleteIfExists = false;
    }

    /**
     * 
     * @param shouldDeleteIfEmpty
     *            the flag value to set
     */
    public void setShouldDeleteIfEmpty(boolean shouldDeleteIfEmpty) {
        this.shouldDeleteIfEmpty = shouldDeleteIfEmpty;
    }

    /**
     * saveState 
     */
    public void setSaveState(boolean saveState) {
        this.saveState = saveState;
    }

    /**
     * headerCallback will be called before writing the first item to file.
     * Newline will be automatically appended after the header is written.
     */
    public void setHeaderCallback(FlatFileHeaderCallback headerCallback) {
        this.headerCallback = headerCallback;
    }

    /**
     * footerCallback will be called after writing the last item to file, but
     * before the file is closed.
     */
    public void setFooterCallback(FlatFileFooterCallback footerCallback) {
        this.footerCallback = footerCallback;
    }

    /**
     * transactional 
     */
    public void setTransactional(boolean transactional) {
        this.transactional = transactional;
    }

    /**
     * Write  
     * ? state ? ?  ? Write  
     *  ?  ? synchronized  ? 
     * 
     * @param items output Stream ?  itmes 
     */
    public synchronized void write(List<? extends T> items) throws Exception {

        if (!getOutputState().isInitialized()) {
            throw new WriterNotOpenException("Writer must be open before it can be written to");
        }

        if (logger.isDebugEnabled()) {
            logger.info("Writing to flat file with " + items.size() + " items.");
        }

        OutputState state = getOutputState();

        StringBuilder lines = new StringBuilder();
        int lineCount = 0;

        for (T item : items) {
            lines.append(lineAggregator.aggregate(item) + lineSeparator);
            lineCount++;
        }

        try {

            state.write(lines.toString());

        } catch (IOException e) {
            throw new WriteFailedException("Could not write data.  The file may be corrupt.", e);
        }
        state.linesWritten += lineCount;
    }

    /**
     * Close  
     * state ? ? fileCount ? ?   state Close 
     *  ?  ? synchronized  ? 
     * @see ItemStream#close()
     */
    public synchronized void close() {
        fileCount--; // increment for close Counting

        if (state != null && fileCount == 0) {
            try {

                if (footerCallback != null && state.outputBufferedWriter != null) {
                    footerCallback.writeFooter(state.outputBufferedWriter);
                    state.outputBufferedWriter.flush();
                }

                fileCloseTime = resource.getFile().lastModified();

                if (state.linesWritten == 0 && shouldDeleteIfEmpty) {
                    try {
                        resource.getFile().delete();
                    } catch (IOException e) {
                        throw new ItemStreamException("Failed to delete empty file on close", e);
                    }
                }

            } catch (ItemStreamException ie) {
                throw ie;
            } catch (IOException e) {
                throw new ItemStreamException("Failed to write footer before closing", e);
            } finally {

                state.close();

            }
        }
    }

    /**
     * open  
     * ??   state ? ?     doOpen 
     *  ?  ? synchronized  ? 
     * @see ItemStream#close()
     */
    public synchronized void open(ExecutionContext executionContext) throws ItemStreamException {
        Assert.notNull(resource, "The resource must be set");

        fileCount++;

        if (!getOutputState().isInitialized()) {
            doOpen(executionContext);
        }
    }

    /**
     * ?? open ? ?
     * state ? BufferedWriter  
     * @see ItemStream#close()
     */
    private void doOpen(ExecutionContext executionContext) throws ItemStreamException {

        OutputState outputState = getOutputState();

        if (executionContext.containsKey(getKey(RESTART_DATA_NAME))) {
            outputState.restoreFrom(executionContext);
        }
        try {
            outputState.initializeBufferedWriter();
            fileOpenTime = resource.getFile().lastModified();
            // ? Thread ?   ? Close ? Stream ?  ??    
            if ((fileOpenTime - fileCloseTime) / 1000.0 < 1) {

                throw new IOException("Failed to initialize writer");
            }
        } catch (IOException ioe) {
            throw new ItemStreamException("Failed to initialize writer", ioe);
        }
        if (outputState.lastMarkedByteOffsetPosition == 0 && !outputState.appending) {
            if (headerCallback != null) {
                try {
                    headerCallback.writeHeader(outputState.outputBufferedWriter);
                    outputState.write(lineSeparator);
                } catch (IOException e) {
                    throw new ItemStreamException("Could not write headers.  The file may be corrupt.", e);
                }
            }
        }
    }

    /**
     * state? ?  ??  position? 
     * @see ItemStream#update(ExecutionContext)
     */
    public void update(ExecutionContext executionContext) {
        if (state == null) {
            throw new ItemStreamException("ItemStream not open or already closed.");
        }

        Assert.notNull(executionContext, "ExecutionContext must not be null");

        if (saveState) {

            try {

                executionContext.putLong(getKey(RESTART_DATA_NAME), state.position());
            } catch (IOException e) {
                throw new ItemStreamException("ItemStream does not return current position properly", e);
            }

            executionContext.putLong(getKey(WRITTEN_STATISTICS_NAME), state.linesWritten);
        }
    }

    /**
     * ??   OutputState ? ?   
     * @return
     */
    private OutputState getOutputState() {
        if (state == null) {
            File file;
            try {
                file = resource.getFile();
            } catch (IOException e) {
                throw new ItemStreamException("Could not convert resource to file: [" + resource + "]", e);
            }
            Assert.state(!file.exists() || file.canWrite(), "Resource is not writable: [" + resource + "]");
            state = new OutputState();
            state.setDeleteIfExists(shouldDeleteIfExists);
            state.setAppendAllowed(append);
            state.setEncoding(encoding);
        }
        return (OutputState) state;
    }

    /**
     * 
     * Data Write   OutputState
     * OutputState  ?? open,update,Write,Close  ?
     * 
     * @author 
     * @since 2012. 07.30
     * @version 1.0
     * @see
     * 
     */
    private class OutputState {

        //  ?? UTF-8.
        private static final String DEFAULT_CHARSET = "UTF-8";

        // FileOutputStream
        private FileOutputStream os;

        // bufferedWriter
        Writer outputBufferedWriter;

        // ? ?
        FileChannel fileChannel;

        //  ?? UTF-8.
        String encoding = DEFAULT_CHARSET;

        // restart 
        boolean restarted = false;

        // Position?   
        long lastMarkedByteOffsetPosition = 0;

        //  ??? 
        long linesWritten = 0;

        // ? ? ?? ??   
        boolean shouldDeleteIfExists = true;

        boolean initialized = false;

        private boolean append = false;

        private boolean appending = false;

        /**
         * ??    position? .
         */
        public long position() throws IOException {
            long pos = 0;

            if (fileChannel == null) {
                return 0;
            }

            outputBufferedWriter.flush();
            pos = fileChannel.position();

            if (transactional) {
                pos += ((TransactionAwareBufferedWriter) outputBufferedWriter).getBufferSize();
            }

            return pos;

        }

        /**
         * AppendAllowed? .
         * 
         * @param append
         */
        public void setAppendAllowed(boolean append) {
            this.append = append;
        }

        /**
         * restart  "RESTART_DATA_NAME"?  ?  .
         * 
         * @param executionContext
         */
        public void restoreFrom(ExecutionContext executionContext) {
            lastMarkedByteOffsetPosition = executionContext.getLong(getKey(RESTART_DATA_NAME));
            restarted = true;
        }

        /**
         * DeleteIfExists? .
         * 
         * @param shouldDeleteIfExists
         */
        public void setDeleteIfExists(boolean shouldDeleteIfExists) {
            this.shouldDeleteIfExists = shouldDeleteIfExists;
        }

        /**
         * ?? .
         * 
         * @param encoding
         */
        public void setEncoding(String encoding) {
            this.encoding = encoding;
        }

        /**
         * outputBufferedWriter 
         * 
         */
        public void close() {

            initialized = false;
            restarted = false;
            try {

                if (outputBufferedWriter != null) {
                    outputBufferedWriter.close();
                }
            } catch (IOException ioe) {
                throw new ItemStreamException("Unable to close the the ItemWriter", ioe);
            } finally {
                if (!transactional) {
                    closeStream();
                }
            }
        }

        /**
         * ??  ? FileOutputStream? .
         */
        private void closeStream() {
            try {
                if (fileChannel != null) {
                    fileChannel.close();
                }
            } catch (IOException ioe) {
                throw new ItemStreamException("Unable to close the the ItemWriter", ioe);
            } finally {

                if (os != null) {
                    try {
                        os.close();
                    } catch (IOException ioe) {
                        logger.debug(ioe);
                    }
                }
            }
        }

        /**
         * line? write  flush .
         * 
         * @param line
         * @throws IOException
         */
        public void write(String line) throws IOException {

            if (!initialized) {
                initializeBufferedWriter();
            }
            outputBufferedWriter.write(line);

            outputBufferedWriter.flush();
        }

        /**
         * file ? ?, ??   .
         * 
         * @throws IOException
         */
        public void truncate() throws IOException {
            fileChannel.truncate(lastMarkedByteOffsetPosition);
            fileChannel.position(lastMarkedByteOffsetPosition);
        }

        /**
         * BufferedWriter  .
         * 
         * @throws IOException
         */
        private void initializeBufferedWriter() throws IOException {
            File file = resource.getFile();

            FileUtils.setUpOutputFile(file, restarted, append, shouldDeleteIfExists);

            os = new FileOutputStream(file.getAbsolutePath(), true);
            fileChannel = os.getChannel();

            outputBufferedWriter = getBufferedWriter(fileChannel, encoding);
            outputBufferedWriter.flush();

            if (append) {
                if (file.length() > 0) {
                    appending = true;
                }
            }

            Assert.state(outputBufferedWriter != null);
            if (restarted) {
                checkFileSize();
                truncate();
            }

            initialized = true;
            linesWritten = 0;
        }

        /**
         * initialized ? .
         */
        public boolean isInitialized() {
            return initialized;
        }

        /**
         * BufferedWriter  .
         */
        private Writer getBufferedWriter(FileChannel fileChannel, String encoding) {
            try {
                Writer writer = Channels.newWriter(fileChannel, encoding);
                if (transactional) {
                    return new TransactionAwareBufferedWriter(writer, new Runnable() {
                        public void run() {
                            closeStream();
                        }
                    });
                } else {
                    return new BufferedWriter(writer);
                }
            } catch (UnsupportedCharsetException ucse) {
                throw new ItemStreamException("Bad encoding configuration for output file " + fileChannel, ucse);
            }
        }

        /**
         * fileSize .
         * 
         * @throws IOException
         */
        private void checkFileSize() throws IOException {
            long size = -1;

            outputBufferedWriter.flush();
            size = fileChannel.size();

            if (size < lastMarkedByteOffsetPosition) {
                throw new ItemStreamException("Current file size is smaller than size at last commit");
            }
        }

    }

}