View Javadoc
1   /*
2    * Copyright 2007 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.coa.service.impl;
17  
18  import java.io.BufferedReader;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.InputStreamReader;
22  import java.util.Calendar;
23  import java.util.Collection;
24  import java.util.Comparator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.SortedMap;
28  import java.util.TreeMap;
29  
30  import org.apache.commons.lang.StringUtils;
31  import org.apache.commons.net.ftp.FTPClient;
32  import org.apache.commons.net.ftp.FTPReply;
33  import org.kuali.ole.coa.batch.CfdaBatchStep;
34  import org.kuali.ole.coa.businessobject.CFDA;
35  import org.kuali.ole.coa.businessobject.CfdaUpdateResults;
36  import org.kuali.ole.coa.service.CfdaService;
37  import org.kuali.ole.sys.OLEConstants;
38  import org.kuali.ole.sys.context.SpringContext;
39  import org.kuali.rice.core.api.datetime.DateTimeService;
40  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
41  import org.kuali.rice.krad.service.BusinessObjectService;
42  
43  import au.com.bytecode.opencsv.CSVReader;
44  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
45  
46  public class CfdaServiceImpl implements CfdaService {
47      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CfdaServiceImpl.class);
48  
49      protected BusinessObjectService businessObjectService;
50      protected static Comparator cfdaComparator;
51  
52      static {
53          cfdaComparator = new Comparator() {
54              @Override
55              public int compare(Object o1, Object o2) {
56                  String lhs = (String) o1;
57                  String rhs = (String) o2;
58                  return lhs.compareTo(rhs);
59              }
60          };
61      }
62  
63      /**
64       * @return
65       * @throws IOException
66       */
67      public SortedMap<String, CFDA> getGovCodes() throws IOException {
68          Calendar calendar = SpringContext.getBean(DateTimeService.class).getCurrentCalendar();
69          SortedMap<String, CFDA> govMap = new TreeMap<String, CFDA>();
70  
71          // ftp://ftp.cfda.gov/programs09187.csv
72          String govURL = SpringContext.getBean(ParameterService.class).getParameterValueAsString(CfdaBatchStep.class, OLEConstants.SOURCE_URL_PARAMETER);
73          String fileName = StringUtils.substringAfterLast(govURL, "/");
74          govURL = StringUtils.substringBeforeLast(govURL, "/");
75          if (StringUtils.contains(govURL, "ftp://")) {
76              govURL = StringUtils.remove(govURL, "ftp://");
77          }
78  
79          // need to pull off the '20' in 2009
80          String year = "" + calendar.get(Calendar.YEAR);
81          year = year.substring(2, 4);
82          fileName = fileName + year;
83  
84          // the last 3 numbers in the file name are the day of the year, but the files are from "yesterday"
85          fileName = fileName + String.format("%03d", calendar.get(Calendar.DAY_OF_YEAR) - 1);
86          fileName = fileName + ".csv";
87  
88          LOG.info("Getting government file: " + fileName + " for update");
89  
90          InputStream inputStream = null;
91          FTPClient ftp = new FTPClient();
92          try {
93              ftp.connect(govURL);
94              int reply = ftp.getReplyCode();
95  
96              if (!FTPReply.isPositiveCompletion(reply)) {
97                  LOG.error("FTP connection to server not established.");
98                  throw new IOException("FTP connection to server not established.");
99              }
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             ftp.enterLocalPassiveMode();
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 = KRADServiceLocatorWeb.getLegacyDataAdapter().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     @Override
160     public CfdaUpdateResults update() throws IOException {
161 
162         CfdaUpdateResults results = new CfdaUpdateResults();
163         Map<String, CFDA> govMap = null;
164 
165         try {
166             govMap = getGovCodes();
167         }
168         catch (IOException ioe) {
169             LOG.error("Error connecting to URL resource: " + ioe.getMessage(), ioe);
170             StringBuilder builder = new StringBuilder();
171             builder.append("No updates took place.\n");
172             builder.append(ioe.getMessage());
173             results.setMessage(builder.toString());
174             return results;
175         }
176         Map<String, CFDA> kfsMap = getKfsCodes();
177 
178         results.setNumberOfRecordsInKfsDatabase(kfsMap.keySet().size());
179         results.setNumberOfRecordsRetrievedFromWebSite(govMap.keySet().size());
180 
181         for (Object key : kfsMap.keySet()) {
182 
183             CFDA cfdaKfs = kfsMap.get(key);
184             CFDA cfdaGov = govMap.get(key);
185 
186             if (cfdaKfs.getCfdaMaintenanceTypeId().startsWith("M")) {
187                 // Leave it alone. It's maintained manually.
188                 results.setNumberOfRecordsNotUpdatedBecauseManual(1 + results.getNumberOfRecordsNotUpdatedBecauseManual());
189             }
190             else if (cfdaKfs.getCfdaMaintenanceTypeId().startsWith("A")) {
191 
192                 if (cfdaGov == null) {
193                     if (cfdaKfs.isActive()) {
194                         cfdaKfs.setActive(false);
195                         KRADServiceLocatorWeb.getLegacyDataAdapter().save(cfdaKfs);
196                         results.setNumberOfRecordsDeactivatedBecauseNoLongerOnWebSite(results.getNumberOfRecordsDeactivatedBecauseNoLongerOnWebSite() + 1);
197                     }
198                     else {
199                         // Leave it alone for historical purposes
200                         results.setNumberOfRecrodsNotUpdatedForHistoricalPurposes(results.getNumberOfRecrodsNotUpdatedForHistoricalPurposes() + 1);
201                     }
202                 }
203                 else {
204                     if (cfdaKfs.isActive()) {
205                         results.setNumberOfRecordsUpdatedBecauseAutomatic(results.getNumberOfRecordsUpdatedBecauseAutomatic() + 1);
206                     }
207                     else {
208                         cfdaKfs.setActive(true);
209                         results.setNumberOfRecordsReActivated(results.getNumberOfRecordsReActivated() + 1);
210                     }
211 
212                     cfdaKfs.setCfdaProgramTitleName(cfdaGov.getCfdaProgramTitleName());
213                     KRADServiceLocatorWeb.getLegacyDataAdapter().save(cfdaKfs);
214                 }
215             }
216 
217             // Remove it from the govMap so we know what codes from the govMap don't already exist in KFS.
218             govMap.remove(key);
219         }
220 
221         // What's left in govMap now is just the codes that don't exist in KFS
222         for (String key : govMap.keySet()) {
223             CFDA cfdaGov = govMap.get(key);
224             cfdaGov.setCfdaMaintenanceTypeId("AUTOMATIC");
225             cfdaGov.setActive(true);
226             KRADServiceLocatorWeb.getLegacyDataAdapter().save(cfdaGov);
227             results.setNumberOfRecordsNewlyAddedFromWebSite(results.getNumberOfRecordsNewlyAddedFromWebSite() + 1);
228         }
229 
230         return results;
231     }
232 
233     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
234         this.businessObjectService = businessObjectService;
235     }
236 
237     @Override
238     public CFDA getByPrimaryId(String cfdaNumber) {
239         if (StringUtils.isBlank(cfdaNumber)) {
240             return null;
241         }
242         return KRADServiceLocatorWeb.getLegacyDataAdapter().findBySinglePrimaryKey(CFDA.class, cfdaNumber.trim());
243     }
244 
245 }