2 * ============LICENSE_START====================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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====================================================
20 */package org.onap.aaf.auth.batch.reports;
22 import java.io.BufferedReader;
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;
35 import java.util.Map.Entry;
37 import java.util.TreeMap;
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;
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;
75 public Notify(AuthzTrans trans) throws APIException, IOException, OrganizationException {
77 access = env.access();
78 session = cluster.connect();
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");
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);
99 StringBuilder sb = new StringBuilder();
100 File fhh = new File(header_html);
102 throw new APIException(header_html + " does not exist");
104 BufferedReader br = new BufferedReader(new FileReader(fhh));
106 while((line=br.readLine())!=null) {
110 String html_css = env.getProperty(HTML_CSS);
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();
116 temp = sb.toString();
118 header = temp.replace("AAF:ENV", batchEnv);
123 // Establish index from header
124 int lastTag = header.lastIndexOf('<');
126 int prevCR = header.lastIndexOf('\n',lastTag);
128 indent = lastTag-prevCR;
130 indent = 6; //arbitrary
139 fhh = new File(footer_html);
141 throw new APIException(footer_html + " does not exist");
144 br = new BufferedReader(new FileReader(fhh));
146 while((line=br.readLine())!=null) {
150 footer = sb.toString();
155 noAvg = trans.env().newTransNoAvg();
156 cqlBatch = new CQLBatch(noAvg.debug(),session);
157 cbl = new CQLBatchLoop(cqlBatch,50,dryRun);
159 lastN = new LastNotified(session);
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.
167 protected void run(AuthzTrans trans) {
169 final Holder<List<String>> info = new Holder<>(null);
170 final Set<String> errorSet = new HashSet<>();
171 String fmt = "%s"+Chrono.dateOnlyStamp()+".csv";
174 // Class Load possible data
175 NotifyBody.load(env.access());
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]));
187 for(NotifyBody nb : NotifyBody.getAll()) {
188 file = new File(logDir,String.format(fmt, nb.name()));
190 trans.info().printf("Processing '%s' in %s",nb.type(),file.getCanonicalPath());
191 notifyFile.add(file);
193 trans.info().printf("No Files found for %s",nb.name());
198 for(File f : notifyFile) {
199 CSV csv = new CSV(env.access(),f);
201 csv.visit(new CSV.Visitor() {
203 public void visit(List<String> row) throws IOException, CadiException {
204 if("info".equals(row.get(0))) {
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);
212 errorSet.add("No NotifyBody defined for " + key);
218 } catch (IOException | CadiException e) {
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());
231 // Do Pending Approval Notifies. We do this separately, because we are consolidating
232 // all the new entries, etc.
234 List<CSV> csvList = new ArrayList<>();
235 for(String s : new String[] {"Approvals","ApprovalsNew"}) {
236 File f = new File(logDir(),String.format(fmt, s));
238 csvList.add(new CSV(access,f));
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());
247 approveCSV.visit(row -> {
253 String user = row.get(1);
254 Pending p = new Pending(row);
255 Pending mp = mpending.get(user);
257 mpending.put(user, p);
259 mp.inc(p); // FYI, unlikely
261 count.set(count.get()+1);
262 } catch (ParseException e) {
263 trans.error().log(e);
268 } catch (IOException | CadiException e) {
269 trans.error().log(e);
274 trans.info().printf("Read %d Reminder Rows", count.get());
276 NotifyPendingApprBody npab = new NotifyPendingApprBody(access);
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();
283 TimeTaken tt = trans.start("Obtain Last Notifications for Approvers", Trans.SUB);
285 lastN.add(mpending.keySet());
292 tt = trans.start("Notify for Pending", Trans.SUB);
293 List<String> idList = new ArrayList<>();
296 for(Entry<String, Pending> es : mpending.entrySet()) {
301 boolean nap = p.newApprovals();
303 Date dateLastNotified = lastN.lastNotified(id,"pending","");
304 if(dateLastNotified==null || dateLastNotified.after(oneWeek) ) {
309 rs.row("appr", id,p.qty(),batchEnv);
310 npab.store(rs.asList());
311 if(notify(noAvg, npab)>0) {
312 npab.record(trans,cbl.inc(), id, idList, lastN);
320 trans.info().printf("Notified %d persons of Pending Approvals", npab.count());
323 } catch (APIException | IOException e1) {
324 trans.error().log(e1);
326 for(String s : errorSet) {
327 trans.audit().log(s);
332 private int notify(AuthzTrans trans, NotifyBody nb) {
333 List<String> toList = new ArrayList<>();
334 List<String> ccList = new ArrayList<>();
335 List<String> idList = new ArrayList<>();
337 String run = nb.type()+nb.name();
338 String test = dryRun?run:null;
341 for(String id : nb.users()) {
346 List<Identity> identities = trans.org().getIDs(trans, id, nb.escalation());
347 if(identities.isEmpty()) {
348 trans.warn().printf("%s is invalid for this Organization. Skipping notification.",id);
350 Identity identity = null;
351 for(Identity ident : identities) {
354 toList.add(ident.email());
356 ccList.add(ident.email());
358 idList.add(ident.fullID());
360 if(identity==null) { // Actually, identity can't be null here, because
361 break; // if(identities.isEmpty() {..} else {... <here>
362 } // So this is here to avoid Sonar false positive only
363 StringBuilder content = new StringBuilder();
364 content.append(String.format(header,version,Identity.mixedCase(identity.firstName())));
366 nb.body(trans, content, indent, this, id);
367 content.append(footer);
369 if(mailer.sendEmail(trans, test, toList, ccList, nb.subject(),content.toString(), urgent)) {
370 nb.record(trans,cbl.inc(), id, idList, lastN);
373 trans.error().log("Mailer failed to send Mail");
375 if(maxEmails>0 && nb.count()>=maxEmails) {
379 } catch (OrganizationException e) {
380 trans.error().log(e);
388 * @see org.onap.aaf.auth.batch.Batch#_close(org.onap.aaf.auth.env.AuthzTrans)
391 protected void _close(AuthzTrans trans) {