Change Sonar flagged code
[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                                  notify(noAvg, nb);
213                          }
214                          
215                          //
216                          // Do Pending Approval Notifies. We do this separately, because we are consolidating
217                          // all the new entries, etc.
218                          //
219                          List<CSV> csvList = new ArrayList<>();
220                          for(String s : new String[] {"Approvals","ApprovalsNew"}) {
221                                  File f = new File(logDir(),String.format(fmt, s));
222                                  if(f.exists()) {
223                                          csvList.add(new CSV(access,f));
224                                  }
225                          }
226                          
227                          Map<String,Pending> mpending = new TreeMap<>();
228                          Holder<Integer> count = new Holder<>(0);
229                          for(CSV approveCSV : csvList) {
230                         TimeTaken tt = trans.start("Load Analyzed Reminders",Trans.SUB,approveCSV.name());
231                         try {
232                                         approveCSV.visit(row -> {
233                                                 switch(row.get(0)) {
234 //                                                      case "info":
235 //                                                              break;
236                                                         case Pending.REMIND:
237                                                                 try {
238                                                                         String user = row.get(1);
239                                                                         Pending p = new Pending(row);
240                                                                         Pending mp = mpending.get(user);
241                                                                         if(mp==null) {
242                                                                                 mpending.put(user, p);
243                                                                         } else {
244                                                                                 mp.inc(p); // FYI, unlikely
245                                                                         }
246                                                                         count.set(count.get()+1);
247                                                                 } catch (ParseException e) {
248                                                                         trans.error().log(e);
249                                                                 } 
250                                                         break;
251                                                 }
252                                         });
253                                 } catch (IOException | CadiException e) {
254                                         trans.error().log(e);
255                         } finally {
256                                 tt.done();
257                         }
258                 }
259                 trans.info().printf("Read %d Reminder Rows", count.get());
260                 
261                 NotifyPendingApprBody npab = new NotifyPendingApprBody(access);
262
263                 GregorianCalendar gc = new GregorianCalendar();
264                 gc.add(GregorianCalendar.DAY_OF_MONTH, 7); // Get from INFO?
265                 Date oneWeek = gc.getTime();
266                 CSV.Saver rs = new CSV.Saver();
267                 
268                 TimeTaken tt = trans.start("Obtain Last Notifications for Approvers", Trans.SUB);
269                 LastNotified lastN;
270                 try {
271                         lastN = new LastNotified(session);
272                         lastN.add(mpending.keySet());
273                 } finally {
274                         tt.done();
275                 }
276                 
277                 Pending p;
278                 final CQLBatchLoop cbl = new CQLBatchLoop(cqlBatch,50,dryRun);
279                 tt = trans.start("Notify for Pending", Trans.SUB);
280                 try {
281                         for(Entry<String, Pending> es : mpending.entrySet()) {
282                                 p = es.getValue();
283                                 boolean nap = p.newApprovals();
284                                 if(!nap) {
285                                 Date dateLastNotified = lastN.lastNotified(es.getKey(),"pending","");
286                                 if(dateLastNotified==null || dateLastNotified.after(oneWeek) ) {
287                                         nap=true;
288                                 }
289                                 }
290                                 if(nap) {
291                                         rs.row("appr", es.getKey(),p.qty(),batchEnv);
292                                         npab.store(rs.asList());
293                                         if(notify(noAvg, npab)>0) {
294                                                 // Update
295                                                 cbl.preLoop();
296                                                 lastN.update(cbl.inc(),es.getKey(),"pending","");
297                                         }
298                                 }
299                         }
300                 } finally {
301                         cbl.flush();
302                         tt.done();
303                 }
304             trans.info().printf("Created %d Notifications", count.get());
305
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          public 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                  String last = null;
324                  
325                  ONE_EMAIL:
326                  for(String id : nb.users()) {
327                          last = id;
328                          toList.clear();
329                          ccList.clear();
330                          try {
331                                  Identity identity = trans.org().getIdentity(trans, id);
332                                  if(identity==null) {
333                                          trans.warn().printf("%s is invalid for this Organization. Skipping notification.",id);
334                                  } else {
335                                          if(!identity.isPerson()) {
336                                                  identity = identity.responsibleTo();
337                                          }
338                                          if(identity==null) {
339                                                  trans.warn().printf("Responsible Identity %s is invalid for this Organization. Skipping notification.",id);
340                                          } else {
341                                                  for(int i=1;i<=nb.escalation();++i) {
342                                                          if(identity != null) {
343                                                                  if(i==1) { // self and Delegates
344                                                                          toList.add(identity.email());
345                                                                          List<String> dels = identity.delegate();
346                                                                          if(dels!=null) {
347                                                                                  for(String d : dels) {
348                                                                                          toList.add(d);
349                                                                                  }
350                                                                          }
351                                                                  } else {
352                                                                          Identity s = identity.responsibleTo();
353                                                                          if(s==null) {
354                                                                                  trans.error().printf("Identity %s has no %s", identity.fullID(),
355                                                                                                  identity.isPerson()?"supervisor":"sponsor");
356                                                                          } else {
357                                                                                  ccList.add(s.email());
358                                                                          }
359                                                                  }
360                                                          }
361                                                  }
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                                                          nb.inc();
371                                                  } else {
372                                                          trans.error().log("Mailer failed to send Mail");
373                                                  }
374                                                  if(maxEmails>0 && nb.count()>=maxEmails) {
375                                                          break ONE_EMAIL;
376                                                  }
377                                          }
378                                  }
379                          } catch (OrganizationException e) {
380                                  trans.error().log(e);
381                          }
382                  }
383                  if(nb.count()<=1) {
384                          trans.info().printf("Notified %s for %s",last,run);
385                  } else {
386                          trans.info().printf("Emailed %d for %s",nb.count(),run);
387                  }
388                  return nb.count();
389          }
390
391  }