/*
* Copyright 2012 C24 Technologies.
*
* 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 biz.c24.io.spring.batch.writer;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.List;
import javax.annotation.PostConstruct;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.annotation.AfterStep;
import org.springframework.batch.core.annotation.BeforeStep;
import org.springframework.batch.item.ItemWriter;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.util.Assert;
import biz.c24.io.api.data.ComplexDataObject;
import biz.c24.io.api.presentation.Sink;
import biz.c24.io.spring.batch.writer.source.WriterSource;
/**
* ItemWriter that sinks and writes ComplexDataObjects to a Writer.
*
* Allows concurrent calls to write but synchronises on individual CDO write to the writer.
*
* @author Andrew Elmore
*/
public class C24ItemWriter implements ItemWriter<ComplexDataObject>{
private Sink templateSink = null;
private ThreadLocal<Sink> sink = new ThreadLocal<Sink>();
private WriterSource writerSource = null;
/**
* Asserts that the object has been properly configured
*/
@PostConstruct
public void validateConfiguration() {
Assert.notNull(templateSink, "Sink must be set");
Assert.notNull(writerSource, "WriterSource must be set");
}
/**
* Initialise our context
*
* @param stepExecution The step execution context
*/
@BeforeStep
public void setup(StepExecution stepExecution) {
writerSource.initialise(stepExecution);
}
/**
* Clean up any resources we're consuming
*/
@AfterStep
public void cleanup() {
writerSource.close();
}
/**
* Writes the contents of the StringWriter to our output file
*
* @param writer The StringWriter to read the data from
*/
private void write(Sink sink) throws IOException {
StringWriter writer = (StringWriter)sink.getWriter();
writer.flush();
StringBuffer buffer = writer.getBuffer();
// Sadly StringBuffer doesn't allow us read-only access to its internal array, so we have to copy
String element = buffer.toString();
Writer outputWriter = writerSource.getWriter();
synchronized(outputWriter) {
outputWriter.write(element);
}
// Reset the buffer for next time
buffer.setLength(0);
}
/**
* Get a thread-safe Sink
*/
private Sink getThreadsafeSink() {
Sink sink = this.sink.get();
if(sink == null) {
// First time this thread has used a sink; create one
sink = (Sink)templateSink.clone();
sink.setWriter(new StringWriter());
this.sink.set(sink);
}
return sink;
}
/*
* (non-Javadoc)
* @see org.springframework.batch.item.ItemWriter#write(java.util.List)
*/
@Override
public void write(List<? extends ComplexDataObject> items) throws Exception {
// Get a sink to use
Sink sink = getThreadsafeSink();
for(ComplexDataObject cdo : items) {
// Sink the CDO
sink.writeObject(cdo);
}
// Now write the whole lot out
write(sink);
}
/**
* The prototype sink used by this C24ItemWriter
*
* @return The prototype sink
*/
public Sink getSink() {
return templateSink;
}
/**
* Provides a prototype sink for this C24ItemWriter to use when sinking ComplexDataObjects
*
* @param sink The prototype sink
*/
@Required
public void setSink(Sink sink) {
templateSink = sink;
}
/**
* Gets the WriterSource used by this C24ItemWriter to get a Writer to persist sunk ComplexDataObjects to
*
* @return The WriterSource used by this C24ItemWriter
*/
public WriterSource getWriterSource() {
return writerSource;
}
/**
* Sets the WriterSource that this C24ItemWriter will use to get a Writer to write sunk ComplexDataObjects to
*
* @param writerSource The WriterSource to use
*/
@Required
public void setWriterSource(WriterSource writerSource) {
this.writerSource = writerSource;
}
}