Java tutorial
/* * 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.web.guide.support; 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 ExPartitionFlatFileItemWriter<T> extends ExecutionContextUserSupport implements ResourceAwareItemWriterItemStream<T>, InitializingBean { private static final boolean DEFAULT_TRANSACTIONAL = true; protected static Log logger = LogFactory.getLog(ExPartitionFlatFileItemWriter.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 ExPartitionFlatFileItemWriter() { setName(ClassUtils.getShortName(ExPartitionFlatFileItemWriter.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(); } } catch (IOException e) { throw new ItemStreamException("Failed to write footer before closing", e); } finally { state.close(); try { fileCloseTime = resource.getFile().lastModified(); } catch (IOException e1) { logger.error(e1); } if (state.linesWritten == 0 && shouldDeleteIfEmpty) { try { resource.getFile().delete(); } catch (IOException e) { throw new ItemStreamException("Failed to delete empty file on close", e); } } state = null; } } } /** * 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 { try { if (os != null) { os.close(); } } catch (IOException ioe) { throw new ItemStreamException("Unable to close the the ItemWriter", 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"); } } } }