/*
* 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.processor;
import java.util.Collection;
import java.util.LinkedList;
import org.springframework.batch.core.annotation.AfterStep;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.beans.factory.annotation.Required;
import biz.c24.io.api.data.ComplexDataObject;
import biz.c24.io.api.data.ValidationEvent;
import biz.c24.io.api.data.ValidationException;
import biz.c24.io.api.data.ValidationListener;
import biz.c24.io.api.data.ValidationManager;
import biz.c24.io.api.presentation.JavaClassSink;
import biz.c24.io.api.transform.Transform;
import biz.c24.io.spring.batch.C24CompoundValidationException;
import biz.c24.io.spring.batch.reader.C24ValidationException;
/**
* A Spring Batch ItemProcesor which invokes a C24 IO Transform to convert a CDO from one model to another.
* Optionally transforms to a target-model compliant Java Bean
*
* @author Andrew Elmore
*/
public class C24TransformItemProcessor implements ItemProcessor<ComplexDataObject, Object> {
/**
* The C24 IO transform to use
*/
private Transform transformer;
/**
* Whether or not to abort on the first failure
*/
private boolean failfast = true;
private ThreadLocal<ValidationManager> validator = null;
/**
* Optional JavaClassSink to use to convert CDOs to POJOs
*/
private JavaClassSink javaSink = null;
/**
* Default constructor. Requires that the transformer is initialised separately.
*/
public C24TransformItemProcessor() {
transformer = null;
}
/**
* Construct a C24TransformItemProcessor
*
* @param transform The iO-generated transform to use
*/
public C24TransformItemProcessor(Transform transform) {
setTransformer(transform);
}
/**
* Construct a C24TransformItemProcessor
*
* @param transform The iO-generated transform to use
* @param validateOutput Whether or not we will validate the result of the transform
*/
public C24TransformItemProcessor(Transform transform, boolean validateOutput) {
this(transform, validateOutput, true);
}
/**
* Construct a C24TransformItemProcessor
*
* @param transform The iO-generated transform to use
* @param validateOutput Whether or not we will validate the result of the transform
* @param failfast Whether validation should abort on first failure or not
*/
public C24TransformItemProcessor(Transform transform, boolean validateOutput, boolean failfast) {
setTransformer(transform);
setValidation(validateOutput);
setFailfast(failfast);
}
/*
* (non-Javadoc)
* @see org.springframework.batch.item.ItemProcessor#process(java.lang.Object)
*/
@Override
public Object process(ComplexDataObject item) throws Exception {
Object[][] transformedObj = transformer.transform(new Object[][]{{item}});
ComplexDataObject result = (ComplexDataObject)transformedObj[0][0];
if(validator != null) {
try {
ValidationManager mgr = validator.get();
if(mgr == null) {
mgr = new ValidationManager();
validator.set(mgr);
}
if(failfast) {
mgr.validateByException(result);
} else {
// Capture all failures
final Collection<ValidationEvent> events = new LinkedList<ValidationEvent>();
ValidationListener listener = new ValidationListener() {
public void validationPassed(ValidationEvent ve) {
}
public void validationFailed(ValidationEvent ve) {
events.add(ve);
}
};
mgr.addValidationListener(listener);
try {
if(!mgr.validateByEvents(result)) {
if(events.size() == 1) {
// Treat it as though we were validating by exception
mgr.setEventBased(false);
mgr.fireValidationEvent(events.iterator().next());
} else {
throw new C24CompoundValidationException(result, events);
}
}
} finally {
mgr.removeValidationListener(listener);
}
}
} catch(ValidationException vEx) {
throw new C24ValidationException("Failed to validate message: " + vEx.getLocalizedMessage(), result, vEx);
}
}
if(javaSink != null) {
return javaSink.convertObject(result);
} else {
return result;
}
}
/**
* Get the C24 IO transformer used by this ItemProcessor
*
* @return The C24 IO transformer
*/
public Transform getTransformer() {
return transformer;
}
/**
* Set the C24 IO transformer that this ItemProcessor will use
*
* @param transformer The C24 IO transformer to use
*/
@Required
public void setTransformer(Transform transformer) {
this.transformer = transformer;
}
/**
* Whether or not this transformer validates the CDOs resulting from the transformation
*
* @return True if if validates generated objects
*/
public boolean isValidating() {
return validator != null;
}
/**
* Turn validation on or off
*
* @param validate
*/
public void setValidation(boolean validate) {
validator = validate? new ThreadLocal<ValidationManager>() : null;
}
/**
* Releases any transient state left over from this transformation step
*/
@AfterStep
public void cleanup() {
// Release any validation managers we're holding; no guarantee the same thread pool will be used next time
if(validator != null) {
validator = new ThreadLocal<ValidationManager>();
}
}
/**
* Returns the sink being used if any
*
* @return The current JavaClassSink
*/
public Class<?> getTargetClass() {
return javaSink != null? javaSink.getRootClass() : null;
}
/**
* Turns on/off returning POJOs or ComplexDataObjects
*
* @param targetClass The Java Bean class to sink to, or CDO if null
*/
public void setTargetClass(Class<?> targetClass) {
if(targetClass != null) {
javaSink = new JavaClassSink();
javaSink.setRootClass(targetClass);
} else {
javaSink = null;
}
}
/**
* Do we abort on first failure or fully validate the object
* @return True iff this processor will abort on first failure
*/
public boolean isFailfast() {
return failfast;
}
/**
* Controls whether this processor aborts on first failure or fully validates the object
* @param failFast
*/
public void setFailfast(boolean failfast) {
this.failfast = failfast;
}
}