1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
|
package org.springframework.ldap.ldif.batch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.file.ResourceAwareItemReaderItemStream;
import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.ldap.core.LdapAttributes;
import org.springframework.ldap.ldif.parser.LdifParser;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* The {@link MappingLdifReader MappingLdifReader} is an adaptation of the {@link org.springframework.batch.item.file.FlatFileItemReader FlatFileItemReader}
* built around an {@link LdifParser LdifParser}. It differs from the standard {@link LdifReader LdifReader} in its ability to map
* {@link LdapAttributes LdapAttributes} objects to POJOs.
* <p>
* The {@link MappingLdifReader MappingLdifReader} <i>requires</i> an {@link RecordMapper RecordMapper} implementation. If mapping
* is not required, the {@link LdifReader LdifReader} should be used instead. It simply returns an {@link LdapAttributes LdapAttributes}
* object which can be consumed and manipulated as necessary by {@link org.springframework.batch.item.ItemProcessor ItemProcessor} or any
* output service.
* <p>
* {@link LdifReader LdifReader} usage is mimics that of the FlatFileItemReader for all intensive purposes. Adjustments have been made to
* process records instead of lines, however. As such, the {@link #recordsToSkip recordsToSkip} attribute indicates the number of records
* from the top of the file that should not be processed. Implementations of the {@link RecordCallbackHandler RecordCallbackHandler}
* interface can be used to execute operations on those skipped records.
* <p>
* As with the {@link org.springframework.batch.item.file.FlatFileItemReader FlatFileItemReader}, the {@link #strict strict} option
* differentiates between whether or not to require the resource to exist before processing. In the case of a value set to false, a warning
* is logged instead of an exception being thrown.
*
* @author Keith Barlow
*
*/
public class MappingLdifReader<T> extends AbstractItemCountingItemStreamItemReader<T>
implements ResourceAwareItemReaderItemStream<T>, InitializingBean {
private static final Log log = LogFactory.getLog(MappingLdifReader.class);
private Resource resource;
private LdifParser ldifParser;
private int recordCount = 0;
private int recordsToSkip = 0;
private boolean strict = true;
private RecordCallbackHandler skippedRecordsCallback;
private RecordMapper<T> recordMapper;
public MappingLdifReader() {
setName(ClassUtils.getShortName(MappingLdifReader.class));
}
/**
* In strict mode the reader will throw an exception on
* {@link #open(org.springframework.batch.item.ExecutionContext)} if the
* input resource does not exist.
* @param strict false by default
*/
public void setStrict(boolean strict) {
this.strict = strict;
}
/**
* {@link RecordCallbackHandler RecordCallbackHandler} implementations can be used to take action on skipped records.
*
* @param skippedRecordsCallback will be called for each one of the initial
* skipped lines before any items are read.
*/
public void setSkippedRecordsCallback(RecordCallbackHandler skippedRecordsCallback) {
this.skippedRecordsCallback = skippedRecordsCallback;
}
/**
* Public setter for the number of lines to skip at the start of a file. Can
* be used if the file contains a header without useful (column name)
* information, and without a comment delimiter at the beginning of the
* lines.
*
* @param recordsToSkip the number of lines to skip
*/
public void setRecordsToSkip(int recordsToSkip) {
this.recordsToSkip = recordsToSkip;
}
/**
* Setter for object mapper. This property is required to be set.
* @param recordMapper maps record to an object
*/
public void setRecordMapper(RecordMapper<T> recordMapper) {
this.recordMapper = recordMapper;
}
@Override
protected void doClose() throws Exception {
if (ldifParser != null) {
ldifParser.close();
}
this.recordCount = 0;
}
@Override
protected void doOpen() throws Exception {
if (resource == null)
throw new IllegalStateException("A resource has not been set.");
if (!resource.exists()) {
if (strict) {
throw new IllegalStateException("Input resource must exist (reader is in 'strict' mode): "+resource);
} else {
log.warn("Input resource does not exist " + resource.getDescription());
return;
}
}
ldifParser.open();
for (int i = 0; i < recordsToSkip; i++) {
LdapAttributes record = ldifParser.getRecord();
if (skippedRecordsCallback != null) {
skippedRecordsCallback.handleRecord(record);
}
}
}
@Override
protected T doRead() throws Exception {
LdapAttributes attributes = null;
try {
if (ldifParser != null) {
while (attributes == null && ldifParser.hasMoreRecords()) {
attributes = ldifParser.getRecord();
}
recordCount++;
return recordMapper.mapRecord(attributes);
}
return null;
} catch(Exception ex){
log.error("Parsing error at record " + recordCount + " in resource=" +
resource.getDescription() + ", input=[" + attributes + "]", ex);
throw ex;
}
}
public void setResource(Resource resource) {
this.resource = resource;
this.ldifParser = new LdifParser(resource);
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(resource, "A resource is required to parse.");
Assert.notNull(ldifParser);
}
}
|