001/* 002 * Copyright 2005-2014 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.springframework.batch.item.ldif; 017 018import org.slf4j.Logger; 019import org.slf4j.LoggerFactory; 020import org.springframework.batch.item.file.ResourceAwareItemReaderItemStream; 021import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader; 022import org.springframework.beans.factory.InitializingBean; 023import org.springframework.core.io.Resource; 024import org.springframework.ldap.core.LdapAttributes; 025import org.springframework.ldap.ldif.parser.LdifParser; 026import org.springframework.util.Assert; 027import org.springframework.util.ClassUtils; 028 029/** 030 * The {@link MappingLdifReader MappingLdifReader} is an adaptation of the {@link org.springframework.batch.item.file.FlatFileItemReader FlatFileItemReader} 031 * built around an {@link LdifParser LdifParser}. It differs from the standard {@link LdifReader LdifReader} in its ability to map 032 * {@link LdapAttributes LdapAttributes} objects to POJOs. 033 * <p> 034 * The {@link MappingLdifReader MappingLdifReader} <i>requires</i> an {@link RecordMapper RecordMapper} implementation. If mapping 035 * is not required, the {@link LdifReader LdifReader} should be used instead. It simply returns an {@link LdapAttributes LdapAttributes} 036 * object which can be consumed and manipulated as necessary by {@link org.springframework.batch.item.ItemProcessor ItemProcessor} or any 037 * output service. 038 * <p> 039 * As with the {@link org.springframework.batch.item.file.FlatFileItemReader FlatFileItemReader}, the {@link #strict strict} option 040 * differentiates between whether or not to require the resource to exist before processing. In the case of a value set to false, a warning 041 * is logged instead of an exception being thrown. 042 * 043 * @author Keith Barlow 044 * 045 */ 046public class MappingLdifReader<T> extends AbstractItemCountingItemStreamItemReader<T> 047 implements ResourceAwareItemReaderItemStream<T>, InitializingBean { 048 049 private static final Logger LOG = LoggerFactory.getLogger(MappingLdifReader.class); 050 051 private Resource resource; 052 053 private LdifParser ldifParser; 054 055 private int recordCount = 0; 056 057 private int recordsToSkip = 0; 058 059 private boolean strict = true; 060 061 private RecordCallbackHandler skippedRecordsCallback; 062 063 private RecordMapper<T> recordMapper; 064 065 public MappingLdifReader() { 066 setName(ClassUtils.getShortName(MappingLdifReader.class)); 067 } 068 069 /** 070 * In strict mode the reader will throw an exception on 071 * {@link #open(org.springframework.batch.item.ExecutionContext)} if the 072 * input resource does not exist. 073 * @param strict false by default 074 */ 075 public void setStrict(boolean strict) { 076 this.strict = strict; 077 } 078 079 /** 080 * {@link RecordCallbackHandler RecordCallbackHandler} implementations can be used to take action on skipped records. 081 * 082 * @param skippedRecordsCallback will be called for each one of the initial 083 * skipped lines before any items are read. 084 */ 085 public void setSkippedRecordsCallback(RecordCallbackHandler skippedRecordsCallback) { 086 this.skippedRecordsCallback = skippedRecordsCallback; 087 } 088 089 /** 090 * Public setter for the number of lines to skip at the start of a file. Can 091 * be used if the file contains a header without useful (column name) 092 * information, and without a comment delimiter at the beginning of the 093 * lines. 094 * 095 * @param recordsToSkip the number of lines to skip 096 */ 097 public void setRecordsToSkip(int recordsToSkip) { 098 this.recordsToSkip = recordsToSkip; 099 } 100 101 /** 102 * Setter for object mapper. This property is required to be set. 103 * @param recordMapper maps record to an object 104 */ 105 public void setRecordMapper(RecordMapper<T> recordMapper) { 106 this.recordMapper = recordMapper; 107 } 108 109 @Override 110 protected void doClose() throws Exception { 111 if (ldifParser != null) { 112 ldifParser.close(); 113 } 114 this.recordCount = 0; 115 } 116 117 @Override 118 protected void doOpen() throws Exception { 119 if (resource == null) 120 throw new IllegalStateException("A resource has not been set."); 121 122 if (!resource.exists()) { 123 if (strict) { 124 throw new IllegalStateException("Input resource must exist (reader is in 'strict' mode): "+resource); 125 } else { 126 LOG.warn("Input resource does not exist " + resource.getDescription()); 127 return; 128 } 129 } 130 131 ldifParser.open(); 132 133 for (int i = 0; i < recordsToSkip; i++) { 134 LdapAttributes record = ldifParser.getRecord(); 135 if (skippedRecordsCallback != null) { 136 skippedRecordsCallback.handleRecord(record); 137 } 138 } 139 } 140 141 @Override 142 protected T doRead() throws Exception { 143 LdapAttributes attributes = null; 144 145 try { 146 if (ldifParser != null) { 147 while (attributes == null && ldifParser.hasMoreRecords()) { 148 attributes = ldifParser.getRecord(); 149 } 150 recordCount++; 151 return recordMapper.mapRecord(attributes); 152 } 153 154 return null; 155 } catch(Exception ex){ 156 LOG.error("Parsing error at record " + recordCount + " in resource=" + 157 resource.getDescription() + ", input=[" + attributes + "]", ex); 158 throw ex; 159 } 160 } 161 162 public void setResource(Resource resource) { 163 this.resource = resource; 164 this.ldifParser = new LdifParser(resource); 165 } 166 167 public void afterPropertiesSet() throws Exception { 168 Assert.notNull(resource, "A resource is required to parse."); 169 Assert.notNull(ldifParser, "A parser is required"); 170 } 171 172}