001 /* 002 * Copyright 2011 The Kuali Foundation. 003 * 004 * Licensed under the Educational Community 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 * http://www.opensource.org/licenses/ecl2.php 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 package org.kuali.kfs.module.cg.service.impl; 017 018 import java.io.BufferedReader; 019 import java.io.IOException; 020 import java.io.InputStream; 021 import java.io.InputStreamReader; 022 import java.util.Calendar; 023 import java.util.Collection; 024 import java.util.Comparator; 025 import java.util.HashMap; 026 import java.util.List; 027 import java.util.Map; 028 import java.util.SortedMap; 029 import java.util.TreeMap; 030 031 import org.apache.commons.lang.StringUtils; 032 import org.apache.commons.net.ftp.FTPClient; 033 import org.apache.commons.net.ftp.FTPReply; 034 import org.kuali.kfs.module.cg.CGConstants; 035 import org.kuali.kfs.module.cg.batch.CfdaBatchStep; 036 import org.kuali.kfs.module.cg.businessobject.CFDA; 037 import org.kuali.kfs.module.cg.businessobject.CfdaUpdateResults; 038 import org.kuali.kfs.module.cg.service.CfdaService; 039 import org.kuali.kfs.sys.KFSPropertyConstants; 040 import org.kuali.kfs.sys.context.SpringContext; 041 import org.kuali.rice.kns.service.BusinessObjectService; 042 import org.kuali.rice.kns.service.DateTimeService; 043 import org.kuali.rice.kns.service.ParameterService; 044 045 import au.com.bytecode.opencsv.CSVReader; 046 047 public class CfdaServiceImpl implements CfdaService { 048 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CfdaServiceImpl.class); 049 050 private BusinessObjectService businessObjectService; 051 protected static Comparator cfdaComparator; 052 053 static { 054 cfdaComparator = new Comparator() { 055 public int compare(Object o1, Object o2) { 056 String lhs = (String) o1; 057 String rhs = (String) o2; 058 return lhs.compareTo(rhs); 059 } 060 }; 061 } 062 063 /** 064 * @return 065 * @throws IOException 066 */ 067 public SortedMap<String, CFDA> getGovCodes() throws IOException { 068 Calendar calendar = SpringContext.getBean(DateTimeService.class).getCurrentCalendar(); 069 SortedMap<String, CFDA> govMap = new TreeMap<String, CFDA>(); 070 071 // ftp://ftp.cfda.gov/programs09187.csv 072 String govURL = SpringContext.getBean(ParameterService.class).getParameterValue(CfdaBatchStep.class, CGConstants.SOURCE_URL_PARAMETER); 073 String fileName = StringUtils.substringAfterLast(govURL, "/"); 074 govURL = StringUtils.substringBeforeLast(govURL, "/"); 075 if (StringUtils.contains(govURL, "ftp://")) { 076 govURL = StringUtils.remove(govURL, "ftp://"); 077 } 078 079 // need to pull off the '20' in 2009 080 String year = "" + calendar.get(Calendar.YEAR); 081 year = year.substring(2, 4); 082 fileName = fileName + year; 083 084 // the last 3 numbers in the file name are the day of the year, but the files are from "yesterday" 085 fileName = fileName + String.format("%03d", calendar.get(Calendar.DAY_OF_YEAR) - 1); 086 fileName = fileName + ".csv"; 087 088 LOG.info("Getting government file: " + fileName + " for update"); 089 090 InputStream inputStream = null; 091 FTPClient ftp = new FTPClient(); 092 try { 093 ftp.connect(govURL); 094 int reply = ftp.getReplyCode(); 095 096 if (!FTPReply.isPositiveCompletion(reply)) { 097 LOG.error("FTP connection to server not established."); 098 throw new IOException("FTP connection to server not established."); 099 } 100 101 boolean loggedIn = ftp.login("anonymous", ""); 102 if (!loggedIn) { 103 LOG.error("Could not login as anonymous."); 104 throw new IOException("Could not login as anonymous."); 105 } 106 107 LOG.info("Successfully connected and logged in"); 108 109 inputStream = ftp.retrieveFileStream(fileName); 110 if (inputStream != null) { 111 LOG.info("reading input stream"); 112 InputStreamReader screenReader = new InputStreamReader(inputStream); 113 BufferedReader screen = new BufferedReader(screenReader); 114 115 CSVReader csvReader = new CSVReader(screenReader, ',', '"', 1); 116 List<String[]> lines = csvReader.readAll(); 117 for (String[] line : lines) { 118 String title = line[0]; 119 String number = line[1]; 120 121 CFDA cfda = new CFDA(); 122 cfda.setCfdaNumber(number); 123 cfda.setCfdaProgramTitleName(title); 124 125 govMap.put(number, cfda); 126 } 127 } 128 129 ftp.logout(); 130 ftp.disconnect(); 131 } 132 finally { 133 if (ftp.isConnected()) { 134 ftp.disconnect(); 135 } 136 } 137 138 return govMap; 139 } 140 141 /** 142 * @return 143 * @throws IOException 144 */ 145 public SortedMap<String, CFDA> getKfsCodes() throws IOException { 146 Collection allCodes = businessObjectService.findAll(CFDA.class); 147 148 SortedMap<String, CFDA> kfsMapAll = new TreeMap<String, CFDA>(cfdaComparator); 149 for (Object o : allCodes) { 150 CFDA c = (CFDA) o; 151 kfsMapAll.put(c.getCfdaNumber(), c); 152 } 153 return kfsMapAll; 154 } 155 156 /** 157 * 158 */ 159 public CfdaUpdateResults update() throws IOException { 160 161 CfdaUpdateResults results = new CfdaUpdateResults(); 162 Map<String, CFDA> govMap = null; 163 164 try { 165 govMap = getGovCodes(); 166 } 167 catch (IOException ioe) { 168 LOG.error("Error connecting to URL resource: " + ioe.getMessage(), ioe); 169 StringBuilder builder = new StringBuilder(); 170 builder.append("No updates took place.\n"); 171 builder.append(ioe.getMessage()); 172 results.setMessage(builder.toString()); 173 return results; 174 } 175 Map<String, CFDA> kfsMap = getKfsCodes(); 176 177 results.setNumberOfRecordsInKfsDatabase(kfsMap.keySet().size()); 178 results.setNumberOfRecordsRetrievedFromWebSite(govMap.keySet().size()); 179 180 for (Object key : kfsMap.keySet()) { 181 182 CFDA cfdaKfs = kfsMap.get(key); 183 CFDA cfdaGov = govMap.get(key); 184 185 if (cfdaKfs.getCfdaMaintenanceTypeId().startsWith("M")) { 186 // Leave it alone. It's maintained manually. 187 results.setNumberOfRecordsNotUpdatedBecauseManual(1 + results.getNumberOfRecordsNotUpdatedBecauseManual()); 188 } 189 else if (cfdaKfs.getCfdaMaintenanceTypeId().startsWith("A")) { 190 191 if (cfdaGov == null) { 192 if (cfdaKfs.isActive()) { 193 cfdaKfs.setActive(false); 194 businessObjectService.save(cfdaKfs); 195 results.setNumberOfRecordsDeactivatedBecauseNoLongerOnWebSite(results.getNumberOfRecordsDeactivatedBecauseNoLongerOnWebSite() + 1); 196 } 197 else { 198 // Leave it alone for historical purposes 199 results.setNumberOfRecrodsNotUpdatedForHistoricalPurposes(results.getNumberOfRecrodsNotUpdatedForHistoricalPurposes() + 1); 200 } 201 } 202 else { 203 if (cfdaKfs.isActive()) { 204 results.setNumberOfRecordsUpdatedBecauseAutomatic(results.getNumberOfRecordsUpdatedBecauseAutomatic() + 1); 205 } 206 else { 207 cfdaKfs.setActive(true); 208 results.setNumberOfRecordsReActivated(results.getNumberOfRecordsReActivated() + 1); 209 } 210 211 cfdaKfs.setCfdaProgramTitleName(cfdaGov.getCfdaProgramTitleName()); 212 businessObjectService.save(cfdaKfs); 213 } 214 } 215 216 // Remove it from the govMap so we know what codes from the govMap don't already exist in KFS. 217 govMap.remove(key); 218 } 219 220 // What's left in govMap now is just the codes that don't exist in KFS 221 for (String key : govMap.keySet()) { 222 CFDA cfdaGov = govMap.get(key); 223 cfdaGov.setCfdaMaintenanceTypeId("Automatic"); 224 cfdaGov.setActive(true); 225 businessObjectService.save(cfdaGov); 226 results.setNumberOfRecordsNewlyAddedFromWebSite(results.getNumberOfRecordsNewlyAddedFromWebSite() + 1); 227 } 228 229 return results; 230 } 231 232 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 233 this.businessObjectService = businessObjectService; 234 } 235 236 public CFDA getByPrimaryId(String cfdaNumber) { 237 if (StringUtils.isBlank(cfdaNumber)) { 238 return null; 239 } 240 return (CFDA) businessObjectService.findByPrimaryKey(CFDA.class, mapPrimaryKeys(cfdaNumber)); 241 } 242 243 protected Map<String, Object> mapPrimaryKeys(String cfdaNumber) { 244 Map<String, Object> primaryKeys = new HashMap(); 245 primaryKeys.put(KFSPropertyConstants.CFDA_NUMBER, cfdaNumber.trim()); 246 return primaryKeys; 247 } 248 249 }