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