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 */ 016 017package org.springframework.batch.item.ldif; 018 019import org.slf4j.Logger; 020import org.slf4j.LoggerFactory; 021import org.springframework.batch.item.file.ResourceAwareItemReaderItemStream; 022import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader; 023import org.springframework.beans.factory.InitializingBean; 024import org.springframework.core.io.Resource; 025import org.springframework.ldap.core.LdapAttributes; 026import org.springframework.ldap.ldif.parser.LdifParser; 027import org.springframework.util.Assert; 028import org.springframework.util.ClassUtils; 029 030/** 031 * The {@link LdifReader LdifReader} is an adaptation of the {@link org.springframework.batch.item.file.FlatFileItemReader FlatFileItemReader} 032 * built around an {@link LdifParser LdifParser}. 033 * <p> 034 * Unlike the {@link org.springframework.batch.item.file.FlatFileItemReader FlatFileItemReader}, the {@link LdifReader LdifReader} 035 * does not require a mapper. Instead, this version of the {@link LdifReader LdifReader} 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. Alternatively, the {@link RecordMapper RecordMapper} interface can be implemented and set in a 038 * {@link MappingLdifReader MappingLdifReader} to map records to objects for return. 039 * <p> 040 * {@link LdifReader LdifReader} usage is mimics that of the {@link org.springframework.batch.item.file.FlatFileItemReader FlatFileItemReader} 041 * for all intensive purposes. Adjustments have been made to process records instead of lines, however. As such, the 042 * {@link #recordsToSkip recordsToSkip} attribute indicates the number of records from the top of the file that should not be processed. 043 * Implementations of the {@link RecordCallbackHandler RecordCallbackHandler} interface can be used to execute operations on those skipped records. 044 * <p> 045 * As with the {@link org.springframework.batch.item.file.FlatFileItemReader FlatFileItemReader}, the {@link #strict strict} option differentiates 046 * 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 047 * an exception being thrown. 048 * 049 * @author Keith Barlow 050 * 051 */ 052public class LdifReader extends AbstractItemCountingItemStreamItemReader<LdapAttributes> 053 implements ResourceAwareItemReaderItemStream<LdapAttributes>, InitializingBean { 054 055 private static final Logger LOG = LoggerFactory.getLogger(LdifReader.class); 056 057 private Resource resource; 058 059 private LdifParser ldifParser; 060 061 private int recordCount = 0; 062 063 private int recordsToSkip = 0; 064 065 private boolean strict = true; 066 067 private RecordCallbackHandler skippedRecordsCallback; 068 069 public LdifReader() { 070 setName(ClassUtils.getShortName(LdifReader.class)); 071 } 072 073 /** 074 * In strict mode the reader will throw an exception on 075 * {@link #open(org.springframework.batch.item.ExecutionContext)} if the 076 * input resource does not exist. 077 * @param strict true by default 078 */ 079 public void setStrict(boolean strict) { 080 this.strict = strict; 081 } 082 083 /** 084 * {@link RecordCallbackHandler RecordCallbackHandler} implementations can be used to take action on skipped records. 085 * 086 * @param skippedRecordsCallback will be called for each one of the initial 087 * skipped lines before any items are read. 088 */ 089 public void setSkippedRecordsCallback(RecordCallbackHandler skippedRecordsCallback) { 090 this.skippedRecordsCallback = skippedRecordsCallback; 091 } 092 093 /** 094 * Public setter for the number of lines to skip at the start of a file. Can 095 * be used if the file contains a header without useful (column name) 096 * information, and without a comment delimiter at the beginning of the 097 * lines. 098 * 099 * @param recordsToSkip the number of lines to skip 100 */ 101 public void setRecordsToSkip(int recordsToSkip) { 102 this.recordsToSkip = recordsToSkip; 103 } 104 105 @Override 106 protected void doClose() throws Exception { 107 if (ldifParser != null) { 108 ldifParser.close(); 109 } 110 this.recordCount = 0; 111 } 112 113 @Override 114 protected void doOpen() throws Exception { 115 if (resource == null) 116 throw new IllegalStateException("A resource has not been set."); 117 118 if (!resource.exists()) { 119 if (strict) { 120 throw new IllegalStateException("Input resource must exist (reader is in 'strict' mode): "+resource); 121 } else { 122 LOG.warn("Input resource does not exist " + resource.getDescription()); 123 return; 124 } 125 } 126 127 ldifParser.open(); 128 129 for (int i = 0; i < recordsToSkip; i++) { 130 LdapAttributes record = ldifParser.getRecord(); 131 if (skippedRecordsCallback != null) { 132 skippedRecordsCallback.handleRecord(record); 133 } 134 } 135 } 136 137 @Override 138 protected LdapAttributes doRead() throws Exception { 139 LdapAttributes attributes = null; 140 141 try { 142 if (ldifParser != null) { 143 while (attributes == null && ldifParser.hasMoreRecords()) { 144 attributes = ldifParser.getRecord(); 145 } 146 recordCount++; 147 } 148 149 return attributes; 150 151 } catch(Exception ex){ 152 LOG.error("Parsing error at record " + recordCount + " in resource=" + 153 resource.getDescription() + ", input=[" + attributes + "]", ex); 154 throw ex; 155 } 156 } 157 158 /** 159 * Establishes the resource that will be used as the input for the LdifReader. 160 * 161 * @param resource the resource that will be read. 162 */ 163 public void setResource(Resource resource) { 164 this.resource = resource; 165 this.ldifParser = new LdifParser(resource); 166 } 167 168 public void afterPropertiesSet() throws Exception { 169 Assert.notNull(resource, "A resource is required to parse."); 170 Assert.notNull(ldifParser, "A parser is required"); 171 } 172 173}