Update Batch from Testing
[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
73          public Notify(AuthzTrans trans) throws APIException, IOException, OrganizationException {
74                  super(trans.env());
75                  access = env.access();
76                  session = cluster.connect();
77
78                  String mailerCls = env.getProperty("MAILER");
79                  String mailFrom = env.getProperty("MAIL_FROM");
80                  String header_html = env.getProperty("HEADER_HTML");
81                  String footer_html = env.getProperty("FOOTER_HTML");
82                  String str = env.getProperty("MAX_EMAIL");
83                  guiURL = env.getProperty("GUI_URL");
84                  maxEmails = str==null||str.isEmpty()?Integer.MAX_VALUE:Integer.parseInt(str);
85                  if(mailerCls==null || mailFrom==null || guiURL==null || header_html==null || footer_html==null) {
86                          throw new APIException("Notify requires MAILER, MAILER_FROM, GUI_URL, HEADER_HTML and FOOTER_HTML properties");
87                  }
88                  try {
89                          Class<?> mailc = Class.forName(mailerCls);
90                          Constructor<?> mailcst = mailc.getConstructor(Access.class);
91                          mailer = (Mailer)mailcst.newInstance(env.access());
92                  } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
93                          throw new APIException("Unable to construct " + mailerCls,e);
94                  }
95
96                  String line;
97                  StringBuilder sb = new StringBuilder();
98                  File fhh = new File(header_html);
99                  if(!fhh.exists()) {
100                          throw new APIException(header_html + " does not exist");
101                  }
102                  BufferedReader br = new BufferedReader(new FileReader(fhh));
103                  try {
104                          while((line=br.readLine())!=null) {
105                                  sb.append(line);
106                                  sb.append('\n');
107                          }
108                          String html_css = env.getProperty(HTML_CSS);
109                          String temp;
110                          int hc = sb.indexOf(HTML_CSS);
111                          if(hc!=0 && html_css!=null) {
112                                  temp = sb.replace(hc,hc+HTML_CSS.length(), html_css).toString();
113                          } else {
114                                  temp = sb.toString();
115                          }
116                          header = temp.replace("AAF:ENV", batchEnv);
117                  } finally {
118                          br.close();
119                  }
120
121                  // Establish index from header
122                  int lastTag = header.lastIndexOf('<');
123                  if(lastTag>0) {
124                          int prevCR = header.lastIndexOf('\n',lastTag);
125                          if(prevCR>0) {
126                                  indent = lastTag-prevCR;
127                          } else {
128                                  indent = 6; //arbitrary
129                          }
130                  } else {
131                          indent = 6;
132                  }
133
134                  urgent = false;
135                  
136                  sb.setLength(0);
137                  fhh = new File(footer_html);
138                  if(!fhh.exists()) {
139                          throw new APIException(footer_html + " does not exist");
140                  }
141
142                  br = new BufferedReader(new FileReader(fhh));
143                  try {
144                          while((line=br.readLine())!=null) {
145                                  sb.append(line);
146                                  sb.append('\n');
147                          }
148                          footer = sb.toString();
149                  } finally {
150                          br.close();
151                  }
152
153                  noAvg = trans.env().newTransNoAvg();
154                  cqlBatch = new CQLBatch(noAvg.debug(),session); 
155          }
156
157          /*
158           * Note: We try to put things related to Notify as Main Class in Run, where we might have put in 
159           * Constructor, so that we can have other Classes call just the "notify" method.
160           */
161          @Override
162          protected void run(AuthzTrans trans) {
163
164                  final Holder<List<String>> info = new Holder<>(null);
165                  final Set<String> errorSet = new HashSet<>();
166                  String fmt = "%s"+Chrono.dateOnlyStamp()+".csv";
167
168                  try {
169                          // Class Load possible data
170                          NotifyBody.load(env.access());
171
172
173                          // Create Intermediate Output 
174                          File logDir = logDir();
175                          Set<File> notifyFile = new HashSet<>();
176                          if(args().length>0) {
177                                  for(int i=0;i<args().length;++i) {
178                                          notifyFile.add(new File(logDir, args()[i]));
179                                  }
180                          } else {
181                                  File file;
182                                  for(NotifyBody nb : NotifyBody.getAll()) {
183                                          file = new File(logDir,String.format(fmt, nb.name()));
184                                          if(file.exists()) {
185                                                  trans.info().printf("Processing '%s' in %s",nb.type(),file.getCanonicalPath());
186                                                  notifyFile.add(file);
187                                          } else {
188                                                  trans.info().printf("No Files found for %s",nb.name());
189                                          }
190                                  }
191                          }
192
193                          for(File f : notifyFile) {
194                                  CSV csv = new CSV(env.access(),f);
195                                  try {
196                                          csv.visit(new CSV.Visitor() {
197                                                  @Override
198                                                  public void visit(List<String> row) throws IOException, CadiException {
199                                                          if("info".equals(row.get(0))) {
200                                                                  info.set(row);
201                                                          }
202                                                          if(info.get()==null) {
203                                                                  throw new CadiException("First line of Feed MUST contain 'info' record");
204                                                          }                                                       String key = row.get(0)+'|'+info.get().get(1);
205                                                          NotifyBody body = NotifyBody.get(key);
206                                                          if(body==null) {
207                                                                  errorSet.add("No NotifyBody defined for " + key);
208                                                          } else {
209                                                                  body.store(row);
210                                                          }
211                                                  }
212                                          });
213                                  } catch (IOException | CadiException e) {
214                                          e.printStackTrace();
215                                  }
216
217                          }      
218
219                          // now create Notification
220                          for(NotifyBody nb : NotifyBody.getAll()) {
221                                  int count = notify(noAvg, nb);
222                                  trans.info().printf("Emailed %d for %s",count,nb.name());
223                          }
224                          
225                          //
226                          // Do Pending Approval Notifies. We do this separately, because we are consolidating
227                          // all the new entries, etc.
228                          //
229                          List<CSV> csvList = new ArrayList<>();
230                          for(String s : new String[] {"Approvals","ApprovalsNew"}) {
231                                  File f = new File(logDir(),String.format(fmt, s));
232                                  if(f.exists()) {
233                                          csvList.add(new CSV(access,f));
234                                  }
235                          }
236                          
237                          Map<String,Pending> mpending = new TreeMap<>();
238                          Holder<Integer> count = new Holder<>(0);
239                          for(CSV approveCSV : csvList) {
240                         TimeTaken tt = trans.start("Load Analyzed Reminders",Trans.SUB,approveCSV.name());
241                         try {
242                                         approveCSV.visit(row -> {
243                                                 switch(row.get(0)) {
244 //                                                      case "info":
245 //                                                              break;
246                                                         case Pending.REMIND:
247                                                                 try {
248                                                                         String user = row.get(1);
249                                                                         Pending p = new Pending(row);
250                                                                         Pending mp = mpending.get(user);
251                                                                         if(mp==null) {
252                                                                                 mpending.put(user, p);
253                                                                         } else {
254                                                                                 mp.inc(p); // FYI, unlikely
255                                                                         }
256                                                                         count.set(count.get()+1);
257                                                                 } catch (ParseException e) {
258                                                                         trans.error().log(e);
259                                                                 } 
260                                                         break;
261                                                 }
262                                         });
263                                 } catch (IOException | CadiException e) {
264                                         trans.error().log(e);
265                         } finally {
266                                 tt.done();
267                         }
268                 }
269                 trans.info().printf("Read %d Reminder Rows", count.get());
270                 
271                 NotifyPendingApprBody npab = new NotifyPendingApprBody(access);
272
273                 GregorianCalendar gc = new GregorianCalendar();
274                 gc.add(GregorianCalendar.DAY_OF_MONTH, 7); // Get from INFO?
275                 Date oneWeek = gc.getTime();
276                 CSV.Saver rs = new CSV.Saver();
277                 
278                 TimeTaken tt = trans.start("Obtain Last Notifications for Approvers", Trans.SUB);
279                 LastNotified lastN;
280                 try {
281                         lastN = new LastNotified(session);
282                         lastN.add(mpending.keySet());
283                 } finally {
284                         tt.done();
285                 }
286                 
287                 Pending p;
288                 final CQLBatchLoop cbl = new CQLBatchLoop(cqlBatch,50,dryRun);
289                 tt = trans.start("Notify for Pending", Trans.SUB);
290                 try {
291                         for(Entry<String, Pending> es : mpending.entrySet()) {
292                                 p = es.getValue();
293                                 boolean nap = p.newApprovals();
294                                 if(!nap) {
295                                 Date dateLastNotified = lastN.lastNotified(es.getKey(),"pending","");
296                                 if(dateLastNotified==null || dateLastNotified.after(oneWeek) ) {
297                                         nap=true;
298                                 }
299                                 }
300                                 if(nap) {
301                                         rs.row("appr", es.getKey(),p.qty(),batchEnv);
302                                         npab.store(rs.asList());
303                                         if(notify(noAvg, npab)>0) {
304                                                 // Update
305                                                 cbl.preLoop();
306                                                 lastN.update(cbl.inc(),es.getKey(),"pending","");
307                                                 npab.inc();
308                                         }
309                                 }
310                         }
311                 } finally {
312                         cbl.flush();
313                         tt.done();
314                 trans.info().printf("Notified %d persons of Pending Approvals", npab.count());
315                 }
316
317                 } catch (APIException | IOException e1) {
318                         trans.error().log(e1);
319                 } finally {
320                          for(String s : errorSet) {
321                                  trans.audit().log(s);
322                          }
323                  }
324          }
325
326          private int notify(AuthzTrans trans, NotifyBody nb) {
327                  List<String> toList = new ArrayList<>();
328                  List<String> ccList = new ArrayList<>();
329
330                  String run = nb.type()+nb.name();
331                  String test = dryRun?run:null;
332                  
333                  ONE_EMAIL:
334                  for(String id : nb.users()) {
335                          toList.clear();
336                          ccList.clear();
337                          try {
338                                  Identity identity = trans.org().getIdentity(trans, id);
339                                  if(identity==null) {
340                                          trans.warn().printf("%s is invalid for this Organization. Skipping notification.",id);
341                                  } else {
342                                          if(!identity.isPerson()) {
343                                                  identity = identity.responsibleTo();
344                                          }
345                                          if(identity==null) {
346                                                  trans.warn().printf("Responsible Identity %s is invalid for this Organization. Skipping notification.",id);
347                                          } else {
348                                                  for(int i=1;i<=nb.escalation();++i) {
349                                                          if(identity != null) {
350                                                                  if(i==1) { // self and Delegates
351                                                                          toList.add(identity.email());
352                                                                          List<String> dels = identity.delegate();
353                                                                          if(dels!=null) {
354                                                                                  for(String d : dels) {
355                                                                                          toList.add(d);
356                                                                                  }
357                                                                          }
358                                                                  } else {
359                                                                          Identity s = identity.responsibleTo();
360                                                                          if(s==null) {
361                                                                                  trans.error().printf("Identity %s has no %s", identity.fullID(),
362                                                                                                  identity.isPerson()?"supervisor":"sponsor");
363                                                                          } else {
364                                                                                  ccList.add(s.email());
365                                                                          }
366                                                                  }
367                                                          }
368                                                  }
369
370                                                  StringBuilder content = new StringBuilder();
371                                                  content.append(String.format(header,version,Identity.mixedCase(identity.firstName())));
372
373                                                  nb.body(trans, content, indent, this, id);
374                                                  content.append(footer);
375
376                                                  if(mailer.sendEmail(trans, test, toList, ccList, nb.subject(),content.toString(), urgent)) {
377                                                          nb.inc();
378                                                  } else {
379                                                          trans.error().log("Mailer failed to send Mail");
380                                                  }
381                                                  if(maxEmails>0 && nb.count()>=maxEmails) {
382                                                          break ONE_EMAIL;
383                                                  }
384                                          }
385                                  }
386                          } catch (OrganizationException e) {
387                                  trans.error().log(e);
388                          }
389                  }
390                  return nb.count();
391          }
392
393  }