Batch, Remove unneeded Classes, refine, etc
[aaf/authz.git] / auth / auth-batch / src / main / java / org / onap / aaf / auth / batch / reports / Notify.java
1 /**
2  * ============LICENSE_START====================================================
3  * org.onap.aaf
4  * ===========================================================================
5  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
6  * ===========================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END====================================================
19  *
20  */package org.onap.aaf.auth.batch.reports;
21
22  import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.FileReader;
25 import java.io.IOException;
26 import java.lang.reflect.Constructor;
27 import java.lang.reflect.InvocationTargetException;
28 import java.text.ParseException;
29 import java.util.ArrayList;
30 import java.util.Date;
31 import java.util.GregorianCalendar;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Map.Entry;
36 import java.util.Set;
37 import java.util.TreeMap;
38
39 import org.onap.aaf.auth.batch.Batch;
40 import org.onap.aaf.auth.batch.approvalsets.Pending;
41 import org.onap.aaf.auth.batch.helpers.CQLBatch;
42 import org.onap.aaf.auth.batch.helpers.CQLBatchLoop;
43 import org.onap.aaf.auth.batch.helpers.LastNotified;
44 import org.onap.aaf.auth.batch.reports.bodies.NotifyBody;
45 import org.onap.aaf.auth.batch.reports.bodies.NotifyPendingApprBody;
46 import org.onap.aaf.auth.env.AuthzTrans;
47 import org.onap.aaf.auth.org.Mailer;
48 import org.onap.aaf.auth.org.Organization.Identity;
49 import org.onap.aaf.auth.org.OrganizationException;
50 import org.onap.aaf.cadi.Access;
51 import org.onap.aaf.cadi.CadiException;
52 import org.onap.aaf.cadi.PropAccess;
53 import org.onap.aaf.cadi.client.Holder;
54 import org.onap.aaf.cadi.util.CSV;
55 import org.onap.aaf.misc.env.APIException;
56 import org.onap.aaf.misc.env.TimeTaken;
57 import org.onap.aaf.misc.env.Trans;
58 import org.onap.aaf.misc.env.util.Chrono;
59
60  public class Notify extends Batch {
61          private static final String HTML_CSS = "HTML_CSS";
62          private final Mailer mailer;
63          private final String header;
64          private final String footer;
65          private final int maxEmails;
66          private final int indent;
67          private final boolean urgent;
68          public final String guiURL;
69          private PropAccess access;
70          private AuthzTrans noAvg;
71          private CQLBatch cqlBatch;
72         private LastNotified  lastN;
73         private CQLBatchLoop cbl;
74
75          public Notify(AuthzTrans trans) throws APIException, IOException, OrganizationException {
76                  super(trans.env());
77                  access = env.access();
78                  session = cluster.connect();
79
80                  String mailerCls = env.getProperty("MAILER");
81                  String mailFrom = env.getProperty("MAIL_FROM");
82                  String header_html = env.getProperty("HEADER_HTML");
83                  String footer_html = env.getProperty("FOOTER_HTML");
84                  String str = env.getProperty("MAX_EMAIL");
85                  guiURL = env.getProperty("GUI_URL");
86                  maxEmails = str==null||str.isEmpty()?Integer.MAX_VALUE:Integer.parseInt(str);
87                  if(mailerCls==null || mailFrom==null || guiURL==null || header_html==null || footer_html==null) {
88                          throw new APIException("Notify requires MAILER, MAILER_FROM, GUI_URL, HEADER_HTML and FOOTER_HTML properties");
89                  }
90                  try {
91                          Class<?> mailc = Class.forName(mailerCls);
92                          Constructor<?> mailcst = mailc.getConstructor(Access.class);
93                          mailer = (Mailer)mailcst.newInstance(env.access());
94                  } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
95                          throw new APIException("Unable to construct " + mailerCls,e);
96                  }
97
98                  String line;
99                  StringBuilder sb = new StringBuilder();
100                  File fhh = new File(header_html);
101                  if(!fhh.exists()) {
102                          throw new APIException(header_html + " does not exist");
103                  }
104                  BufferedReader br = new BufferedReader(new FileReader(fhh));
105                  try {
106                          while((line=br.readLine())!=null) {
107                                  sb.append(line);
108                                  sb.append('\n');
109                          }
110                          String html_css = env.getProperty(HTML_CSS);
111                          String temp;
112                          int hc = sb.indexOf(HTML_CSS);
113                          if(hc!=0 && html_css!=null) {
114                                  temp = sb.replace(hc,hc+HTML_CSS.length(), html_css).toString();
115                          } else {
116                                  temp = sb.toString();
117                          }
118                          header = temp.replace("AAF:ENV", batchEnv);
119                  } finally {
120                          br.close();
121                  }
122
123                  // Establish index from header
124                  int lastTag = header.lastIndexOf('<');
125                  if(lastTag>0) {
126                          int prevCR = header.lastIndexOf('\n',lastTag);
127                          if(prevCR>0) {
128                                  indent = lastTag-prevCR;
129                          } else {
130                                  indent = 6; //arbitrary
131                          }
132                  } else {
133                          indent = 6;
134                  }
135
136                  urgent = false;
137                  
138                  sb.setLength(0);
139                  fhh = new File(footer_html);
140                  if(!fhh.exists()) {
141                          throw new APIException(footer_html + " does not exist");
142                  }
143
144                  br = new BufferedReader(new FileReader(fhh));
145                  try {
146                          while((line=br.readLine())!=null) {
147                                  sb.append(line);
148                                  sb.append('\n');
149                          }
150                          footer = sb.toString();
151                  } finally {
152                          br.close();
153                  }
154
155                  noAvg = trans.env().newTransNoAvg();
156                  cqlBatch = new CQLBatch(noAvg.debug(),session); 
157                  cbl = new CQLBatchLoop(cqlBatch,50,dryRun);
158                  
159                  lastN = new LastNotified(session);
160          }
161
162          /*
163           * Note: We try to put things related to Notify as Main Class in Run, where we might have put in 
164           * Constructor, so that we can have other Classes call just the "notify" method.
165           */
166          @Override
167          protected void run(AuthzTrans trans) {
168
169                  final Holder<List<String>> info = new Holder<>(null);
170                  final Set<String> errorSet = new HashSet<>();
171                  String fmt = "%s"+Chrono.dateOnlyStamp()+".csv";
172
173                  try {
174                          // Class Load possible data
175                          NotifyBody.load(env.access());
176
177
178                          // Create Intermediate Output 
179                          File logDir = logDir();
180                          Set<File> notifyFile = new HashSet<>();
181                          if(args().length>0) {
182                                  for(int i=0;i<args().length;++i) {
183                                          notifyFile.add(new File(logDir, args()[i]));
184                                  }
185                          } else {
186                                  File file;
187                                  for(NotifyBody nb : NotifyBody.getAll()) {
188                                          file = new File(logDir,String.format(fmt, nb.name()));
189                                          if(file.exists()) {
190                                                  trans.info().printf("Processing '%s' in %s",nb.type(),file.getCanonicalPath());
191                                                  notifyFile.add(file);
192                                          } else {
193                                                  trans.info().printf("No Files found for %s",nb.name());
194                                          }
195                                  }
196                          }
197
198                          for(File f : notifyFile) {
199                                  CSV csv = new CSV(env.access(),f);
200                                  try {
201                                          csv.visit(new CSV.Visitor() {
202                                                  @Override
203                                                  public void visit(List<String> row) throws IOException, CadiException {
204                                                          if("info".equals(row.get(0))) {
205                                                                  info.set(row);
206                                                          }
207                                                          if(info.get()==null) {
208                                                                  throw new CadiException("First line of Feed MUST contain 'info' record");
209                                                          }                                                       String key = row.get(0)+'|'+info.get().get(1);
210                                                          NotifyBody body = NotifyBody.get(key);
211                                                          if(body==null) {
212                                                                  errorSet.add("No NotifyBody defined for " + key);
213                                                          } else {
214                                                                  body.store(row);
215                                                          }
216                                                  }
217                                          });
218                                  } catch (IOException | CadiException e) {
219                                          e.printStackTrace();
220                                  }
221
222                          }      
223
224                          // now create Notification
225                          for(NotifyBody nb : NotifyBody.getAll()) {
226                                  int count = notify(noAvg, nb);
227                                  trans.info().printf("Emailed %d for %s",count,nb.name());
228                          }
229                          
230                          //
231                          // Do Pending Approval Notifies. We do this separately, because we are consolidating
232                          // all the new entries, etc.
233                          //
234                          List<CSV> csvList = new ArrayList<>();
235                          for(String s : new String[] {"Approvals","ApprovalsNew"}) {
236                                  File f = new File(logDir(),String.format(fmt, s));
237                                  if(f.exists()) {
238                                          csvList.add(new CSV(access,f));
239                                  }
240                          }
241                          
242                          Map<String,Pending> mpending = new TreeMap<>();
243                          Holder<Integer> count = new Holder<>(0);
244                          for(CSV approveCSV : csvList) {
245                         TimeTaken tt = trans.start("Load Analyzed Reminders",Trans.SUB,approveCSV.name());
246                         try {
247                                         approveCSV.visit(row -> {
248                                                 switch(row.get(0)) {
249 //                                                      case "info":
250 //                                                              break;
251                                                         case Pending.REMIND:
252                                                                 try {
253                                                                         String user = row.get(1);
254                                                                         Pending p = new Pending(row);
255                                                                         Pending mp = mpending.get(user);
256                                                                         if(mp==null) {
257                                                                                 mpending.put(user, p);
258                                                                         } else {
259                                                                                 mp.inc(p); // FYI, unlikely
260                                                                         }
261                                                                         count.set(count.get()+1);
262                                                                 } catch (ParseException e) {
263                                                                         trans.error().log(e);
264                                                                 } 
265                                                         break;
266                                                 }
267                                         });
268                                 } catch (IOException | CadiException e) {
269                                         trans.error().log(e);
270                         } finally {
271                                 tt.done();
272                         }
273                 }
274                 trans.info().printf("Read %d Reminder Rows", count.get());
275                 
276                 NotifyPendingApprBody npab = new NotifyPendingApprBody(access);
277
278                 GregorianCalendar gc = new GregorianCalendar();
279                 gc.add(GregorianCalendar.DAY_OF_MONTH, 7); // Get from INFO?
280                 Date oneWeek = gc.getTime();
281                 CSV.Saver rs = new CSV.Saver();
282                 
283                 TimeTaken tt = trans.start("Obtain Last Notifications for Approvers", Trans.SUB);
284                 try {
285                         lastN.add(mpending.keySet());
286                 } finally {
287                         tt.done();
288                 }
289                 
290                 Pending p;
291                 
292                 tt = trans.start("Notify for Pending", Trans.SUB);
293                 List<String> idList = new ArrayList<String>();
294                 String id;
295                 try {
296                         for(Entry<String, Pending> es : mpending.entrySet()) {
297                                 id = es.getKey();
298                                 idList.clear();
299                                 idList.add(id);
300                                 p = es.getValue();
301                                 boolean nap = p.newApprovals();
302                                 if(!nap) {
303                                 Date dateLastNotified = lastN.lastNotified(id,"pending","");
304                                 if(dateLastNotified==null || dateLastNotified.after(oneWeek) ) {
305                                         nap=true;
306                                 }
307                                 }
308                                 if(nap) {
309                                         rs.row("appr", id,p.qty(),batchEnv);
310                                         npab.store(rs.asList());
311                                         if(notify(noAvg, npab)>0) {
312                                                 // Update
313                                                 cbl.preLoop();
314 //                                              lastN.update(cbl.inc(),key,"pending","");
315                                                 npab.record(trans,cbl.inc(), id, idList, lastN);
316                                                 npab.inc();
317                                         }
318                                 }
319                         }
320                 } finally {
321                         cbl.flush();
322                         tt.done();
323                 trans.info().printf("Notified %d persons of Pending Approvals", npab.count());
324                 }
325
326                 } catch (APIException | IOException e1) {
327                         trans.error().log(e1);
328                 } finally {
329                          for(String s : errorSet) {
330                                  trans.audit().log(s);
331                          }
332                  }
333          }
334
335          private int notify(AuthzTrans trans, NotifyBody nb) {
336                  List<String> toList = new ArrayList<>();
337                  List<String> ccList = new ArrayList<>();
338                  List<String> idList = new ArrayList<>();
339
340                  String run = nb.type()+nb.name();
341                  String test = dryRun?run:null;
342                  
343                  ONE_EMAIL:
344                  for(String id : nb.users()) {
345                          toList.clear();
346                          ccList.clear();
347                          idList.clear();
348                          try {
349                                  List<Identity> identities = trans.org().getIDs(trans, id, nb.escalation());
350                                  if(identities.isEmpty()) {
351                                          trans.warn().printf("%s is invalid for this Organization. Skipping notification.",id);
352                                  } else {
353                                          Identity identity = null;
354                                          for(Identity ident : identities) {
355                                                  if(identity==null) {
356                                                          identity = ident;
357                                                          toList.add(ident.email());
358                                                  } else {
359                                                          ccList.add(ident.email());
360                                                  }
361                                                  idList.add(ident.fullID());
362                                          }
363                                          StringBuilder content = new StringBuilder();
364                                          content.append(String.format(header,version,Identity.mixedCase(identity.firstName())));
365
366                                          nb.body(trans, content, indent, this, id);
367                                          content.append(footer);
368
369                                          if(mailer.sendEmail(trans, test, toList, ccList, nb.subject(),content.toString(), urgent)) {
370                                                 cbl.preLoop();
371                                                 nb.record(trans,cbl.inc(), id, idList, lastN);
372                                                 nb.inc();
373                                          } else {
374                                                  trans.error().log("Mailer failed to send Mail");
375                                          }
376                                          if(maxEmails>0 && nb.count()>=maxEmails) {
377                                                  break ONE_EMAIL;
378                                          }
379                                  }
380                          } catch (OrganizationException e) {
381                                  trans.error().log(e);
382                          }
383                  }
384                  cbl.flush();
385                  return nb.count();
386          }
387
388         /* (non-Javadoc)
389          * @see org.onap.aaf.auth.batch.Batch#_close(org.onap.aaf.auth.env.AuthzTrans)
390          */
391         @Override
392         protected void _close(AuthzTrans trans) {
393                 cbl.flush();
394         }
395
396  }