summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/Batch.java9
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/approvalsets/ApprovalSet.java14
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/approvalsets/Pending.java18
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/approvalsets/URApprovalSet.java8
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/CQLBatch.java4
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/ExpireRange.java11
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Future.java18
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/LastNotified.java72
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Notification.java211
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/UserRole.java22
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Analyze.java200
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Notify.java129
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/NotifyCredBody.java22
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/OneMonthNotifyCredBody.java8
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/TwoMonthNotifyCredBody.java10
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/TwoWeeksNotifyCredBody.java8
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/update/Approvals.java150
-rw-r--r--auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/update/Extend.java2
-rw-r--r--auth/auth-batch/src/test/java/org/onap/aaf/auth/batch/test/JU_NotificationTest.java77
-rw-r--r--auth/auth-cass/cass_init/init.cql9
-rw-r--r--auth/auth-cass/cass_init/init2_10.cql10
-rw-r--r--auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/ca/LocalCA.java2
-rw-r--r--auth/auth-gui/src/main/java/org/onap/aaf/auth/gui/pages/ApprovalForm.java40
-rw-r--r--auth/auth-gui/src/main/java/org/onap/aaf/auth/gui/table/TextToolTipCell.java51
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/filter/CadiFilter.java9
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/util/CSV.java23
26 files changed, 566 insertions, 571 deletions
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/Batch.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/Batch.java
index 1c65c058..a588b808 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/Batch.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/Batch.java
@@ -149,7 +149,7 @@ public abstract class Batch {
}
protected abstract void run(AuthzTrans trans);
- protected abstract void _close(AuthzTrans trans);
+ protected void _close(AuthzTrans trans) {}
public String[] args() {
return env.get(ssargs);
@@ -363,7 +363,12 @@ public abstract class Batch {
public final void close(AuthzTrans trans) {
_close(trans);
- cluster.close();
+ if(session!=null) {
+ session.close();
+ }
+ if(cluster!=null) {
+ cluster.close();
+ }
}
public static void main(String[] args) {
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/approvalsets/ApprovalSet.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/approvalsets/ApprovalSet.java
index b7176c26..500906d0 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/approvalsets/ApprovalSet.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/approvalsets/ApprovalSet.java
@@ -25,7 +25,9 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.GregorianCalendar;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.UUID;
import org.onap.aaf.auth.dao.cass.ApprovalDAO;
@@ -93,4 +95,16 @@ public class ApprovalSet {
}
return errs==null?Result.ok():Result.err(Result.ERR_Backend,errs.toString());
}
+
+ public boolean hasApprovals() {
+ return !ladd.isEmpty();
+ }
+
+ public Set<String> approvers() {
+ Set<String> rv = new HashSet<>();
+ for(ApprovalDAO.Data app : ladd) {
+ rv.add(app.approver);
+ }
+ return rv;
+ }
} \ No newline at end of file
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/approvalsets/Pending.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/approvalsets/Pending.java
index 3072038a..5d720c2f 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/approvalsets/Pending.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/approvalsets/Pending.java
@@ -35,15 +35,6 @@ public class Pending {
Date earliest;
/**
- * Use this Constructor when there is no Last Notified Date
- */
- public Pending() {
- qty = 1;
- hasNew = true;
- earliest = null;
- }
-
- /**
* Use this constructor to indicate when last Notified
* @param last_notified
*/
@@ -84,6 +75,11 @@ public class Pending {
public void inc(Pending value) {
qty+=value.qty;
+ if(earliest==null) {
+ earliest = value.earliest;
+ } else if(value.earliest!=null && value.earliest.before(earliest)) {
+ earliest = value.earliest;
+ }
}
public void earliest(Date lastnotified) {
@@ -106,4 +102,8 @@ public class Pending {
return hasNew;
}
+ public static Pending create() {
+ return new Pending((Date)null);
+ }
+
} \ No newline at end of file
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/approvalsets/URApprovalSet.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/approvalsets/URApprovalSet.java
index 2c1ffe6d..7f7bff28 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/approvalsets/URApprovalSet.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/approvalsets/URApprovalSet.java
@@ -49,8 +49,12 @@ public class URApprovalSet extends ApprovalSet {
UserRoleDAO.Data urdd = lurdd.load();
setConstruct(urdd.bytify());
setMemo(getMemo(urdd));
- setExpires(org.expiration(null, Organization.Expiration.UserInRole));
- setTargetKey(urdd.role);
+ GregorianCalendar expires = org.expiration(null, Organization.Expiration.UserInRole);
+ if(urdd.expires.before(expires.getTime())) {
+ expires.setTime(urdd.expires);
+ }
+ setExpires(expires);
+ setTargetKey(urdd.user+'|'+urdd.role);
setTargetDate(urdd.expires);
Result<RoleDAO.Data> r = dv.roleByName(trans, urdd.role);
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/CQLBatch.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/CQLBatch.java
index 738c5345..9f685ad4 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/CQLBatch.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/CQLBatch.java
@@ -84,4 +84,8 @@ public class CQLBatch {
}
execute(dryRun);
}
+
+ public String toString() {
+ return sb.toString();
+ }
}
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/ExpireRange.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/ExpireRange.java
index 73bff6e6..13d74c8f 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/ExpireRange.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/ExpireRange.java
@@ -40,12 +40,11 @@ public class ExpireRange {
public static final String ONE_WEEK = "OneWeek";
private static final String AAF_BATCH_RANGE = "aaf_batch_range.";
public Map<String,List<Range>> ranges;
- public final Date now;
+ public static final Date now = new Date();
private Range delRange;
public ExpireRange(final Access access) {
- now = new Date();
ranges = new HashMap<>();
int i=0;
String prop = access.getProperty(AAF_BATCH_RANGE + i,null);
@@ -70,6 +69,10 @@ public class ExpireRange {
}
}
+ public static Range newFutureRange() {
+ return new Range("Approval",1,1,0,0,GregorianCalendar.MONTH,1);
+ }
+
public Set<String> names() {
Set<String> names = new HashSet<>();
for(List<Range> lr : ranges.values()) {
@@ -90,7 +93,7 @@ public class ExpireRange {
return rv;
}
- public class Range {
+ public static class Range {
private final String name;
private final int reportingLevel;
private final int interval; // in Days
@@ -138,7 +141,7 @@ public class ExpireRange {
return end;
}
- private boolean inRange(final Date date) {
+ public boolean inRange(final Date date) {
if(date==null) {
return false;
} else {
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Future.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Future.java
index 13f81938..4f87e330 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Future.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Future.java
@@ -23,6 +23,7 @@
package org.onap.aaf.auth.batch.helpers;
+import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Date;
@@ -32,6 +33,7 @@ import java.util.TreeMap;
import java.util.UUID;
import org.onap.aaf.auth.dao.cass.FutureDAO;
+import org.onap.aaf.auth.dao.cass.UserRoleDAO;
import org.onap.aaf.auth.env.AuthzTrans;
import org.onap.aaf.auth.layer.Result;
import org.onap.aaf.cadi.util.CSV;
@@ -90,7 +92,21 @@ public class Future implements CacheChange.Data, Comparable<Future> {
fdd.start = start;
fdd.expires = expires;
fdd.construct = construct;
- role = Approval.roleFromMemo(memo);
+ String role = null;
+ switch(target) {
+ case "user_role":
+ UserRoleDAO.Data urdd = new UserRoleDAO.Data();
+ try {
+ urdd.reconstitute(construct);
+ fdd.target_key = urdd.user + '|' + urdd.role;
+ fdd.target_date=urdd.expires;
+ role=urdd.role;
+ } catch (IOException e) {
+ e.printStackTrace(System.err);
+ }
+ break;
+ }
+ this.role = role;
}
public final UUID id() {
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/LastNotified.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/LastNotified.java
index 22231f3a..0539fcdc 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/LastNotified.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/LastNotified.java
@@ -28,7 +28,7 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
-import org.onap.aaf.auth.batch.helpers.Notification.TYPE;
+import org.onap.aaf.auth.dao.cass.UserRoleDAO;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
@@ -37,6 +37,7 @@ import com.datastax.driver.core.Session;
public class LastNotified {
private Map<String,Date> lastNotified = new TreeMap<>();
private Session session;
+ public static final Date never = new Date(0);
public LastNotified(Session session) {
this.session = session;
@@ -44,7 +45,7 @@ public class LastNotified {
public void add(Set<String> users) {
StringBuilder query = new StringBuilder();
- startNotifyQuery(query);
+ startQuery(query);
int cnt = 0;
for(String user : users) {
if(++cnt>1) {
@@ -54,37 +55,74 @@ public class LastNotified {
query.append(user);
query.append('\'');
if(cnt>=30) {
- endNotifyQuery(query, Notification.TYPE.OA);
+ endQuery(query);
add(session.execute(query.toString()),lastNotified);
query.setLength(0);
- startNotifyQuery(query);
+ startQuery(query);
cnt=0;
}
}
if(cnt>0) {
- endNotifyQuery(query, Notification.TYPE.OA);
+ endQuery(query);
add(session.execute(query.toString()),lastNotified);
}
}
- public Date lastNotified(String user) {
- return lastNotified.get(user);
+ /**
+ * Note: target_key CAN also contain a Pipe.
+ *
+ * @param user
+ * @param target
+ * @param target_key
+ * @return
+ */
+ public Date lastNotified(String user, String target, String target_key) {
+ String key = user + '|' + target + '|' + target_key;
+ return lastNotified(key);
}
- private void add(ResultSet result, Map<String, Date> lastNotified) {
+ public Date lastNotified(String key) {
+ Date rv = lastNotified.get(key);
+ if(rv==null) {
+ rv = never;
+ lastNotified.put(key, rv);
+ }
+ return rv;
+ }
+
+ private Date add(ResultSet result, Map<String, Date> lastNotified) {
+ Date last = null;
for(Iterator<Row> iter = result.iterator(); iter.hasNext();) {
Row r = iter.next();
- lastNotified.put(r.getString(0), r.getTimestamp(1));
+ String key = r.getString(0) + '|' +
+ r.getString(1) + '|' +
+ r.getString(2);
+
+ lastNotified.put(key, last=r.getTimestamp(3));
}
+ return last;
}
- private void startNotifyQuery(StringBuilder query) {
- query.append("SELECT user,last FROM authz.notify WHERE user in (");
+ private void startQuery(StringBuilder query) {
+ query.append("SELECT user,target,key,last FROM authz.notified WHERE user in (");
}
-
- private void endNotifyQuery(StringBuilder query, TYPE oa) {
- query.append(") AND type=");
- query.append(oa.idx());
- query.append(';');
- }
+
+ private void endQuery(StringBuilder query) {
+ query.append(");");
+ }
+
+ public void update(StringBuilder query,String user, String target, String key) {
+ query.append("UPDATE authz.notified SET last=dateof(now()) WHERE user='");
+ query.append(user);
+ query.append("' AND target='");
+ query.append(target);
+ query.append("' AND key='");
+ query.append(key);
+ query.append("';");
+ }
+
+ public static String newKey(UserRoleDAO.Data urdd) {
+ return urdd.user + "|ur|" + urdd.role;
+ }
+
}
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Notification.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Notification.java
deleted file mode 100644
index ae0d37b5..00000000
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Notification.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/**
- * ============LICENSE_START====================================================
- * org.onap.aaf
- * ===========================================================================
- * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
- * ===========================================================================
- * Modifications Copyright (C) 2018 IBM.
- * ===========================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END====================================================
- *
- */
-
-package org.onap.aaf.auth.batch.helpers;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.TreeMap;
-
-import org.onap.aaf.auth.batch.actions.Message;
-import org.onap.aaf.auth.env.AuthzTrans;
-import org.onap.aaf.auth.org.Organization;
-import org.onap.aaf.misc.env.Env;
-import org.onap.aaf.misc.env.TimeTaken;
-import org.onap.aaf.misc.env.Trans;
-import org.onap.aaf.misc.env.util.Chrono;
-
-import com.datastax.driver.core.ResultSet;
-import com.datastax.driver.core.Row;
-import com.datastax.driver.core.Session;
-import com.datastax.driver.core.SimpleStatement;
-import com.datastax.driver.core.Statement;
-
-public class Notification {
- public enum TYPE {
- OA("Owner Approval",1),SA("Supervisor Approval",2),CN("Credential Expiration",20);
-
- private String desc;
- private int type;
-
- private TYPE(String desc,int type) {
- this.desc = desc;
- this.type = type;
- }
-
- public String desc() {
- return desc;
- }
-
- public int idx() {
- return type;
- }
-
- public static TYPE get(int idx) {
- for (TYPE nt : TYPE.values()) {
- if (idx==nt.type) {
- return nt;
- }
- }
- return null;
- }
- }
-
-
- public static final TreeMap<String,List<Notification>> data = new TreeMap<>();
- public static final Date now = new Date();
-
- public final String user;
- public final TYPE type;
- public Date last;
- public int checkSum;
- public Message msg;
- private int current;
- public Organization org;
- public int count;
-
- public static Creator<Notification> v2_0_18 = new Creator<Notification>() {
- @Override
- public Notification create(Row row) {
- int idx =row.getInt(1);
- TYPE typeCreator = TYPE.get(idx);
- if (typeCreator==null) {
- return null;
- }
- return new Notification(row.getString(0), typeCreator, row.getTimestamp(2), row.getInt(3));
- }
-
- @Override
- public String select() {
- return "SELECT user,type,last,checksum FROM authz.notify LIMIT 100000";
- }
- };
-
- private Notification(String user, TYPE nt, Date last, int checksum) {
- this.user = user;
- this.type = nt;
- this.last = last;
- this.checkSum = checksum;
- current = 0;
- count = 0;
- }
-
- public static void load(Trans trans, Session session, Creator<Notification> creator ) {
- trans.info().log( "query: " + creator.select() );
- TimeTaken tt = trans.start("Load Notify", Env.REMOTE);
-
- ResultSet results;
- try {
- Statement stmt = new SimpleStatement(creator.select());
- results = session.execute(stmt);
- } finally {
- tt.done();
- }
- int count = 0;
- tt = trans.start("Process Notify", Env.SUB);
-
- try {
- for (Row row : results.all()) {
- ++count;
- try {
- Notification not = creator.create(row);
- List<Notification> ln = data.get(not.user);
- if (ln==null) {
- ln = new ArrayList<>();
- data.put(not.user, ln);
- }
- ln.add(not);
- } finally {
- tt.done();
- }
- }
- } finally {
- tt.done();
- trans.info().log("Found",count,"Notify Records");
- }
- }
-
- public static Notification get(String user, TYPE type) {
- List<Notification> ln = data.get(user);
- if (ln!=null) {
- for (Notification n : ln) {
- if (type.equals(n.type)) {
- return n;
- }
- }
- }
- return null;
- }
-
- public static Notification create(String user, TYPE type) {
- return new Notification(user,type,null,0);
- }
-
-
- public void set(Message msg) {
- this.msg = msg;
- }
-
- public int checksum() {
- if (msg==null) {
- current=0;
- } else if (current==0) {
- for (String l : msg.lines) {
- for (byte b : l.getBytes()) {
- current+=b;
- }
- }
- }
- return current;
- }
-
- public boolean update(AuthzTrans trans, Session session, boolean dryRun) {
- checksum();
- if (last==null || current==0 || current!=checkSum) {
- last = now;
- current = checksum();
- String update = "UPDATE authz.notify SET " +
- "last = '" + Chrono.utcStamp(last) +
- "', checksum=" +
- current +
- " WHERE user='" +
- user +
- "' AND type=" +
- type.idx() +
- ";";
- if (dryRun) {
- trans.info().log("Would",update);
- } else {
- session.execute(update);
- }
- return true;
- }
- return false;
- }
-
- public String toString() {
- return "\"" + user + "\",\"" + type.name() + "\",\""
- + Chrono.dateTime(last)+ "\", " + checkSum;
- }
-} \ No newline at end of file
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/UserRole.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/UserRole.java
index 55dd1e7c..343a0e2b 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/UserRole.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/UserRole.java
@@ -122,12 +122,12 @@ public class UserRole implements Cloneable, CacheChange.Data {
}
public static void loadOneUser(Trans trans, Session session, Creator<UserRole> creator, String user, Visitor<UserRole> visitor ) {
- load(trans,session,creator,"role='"+ user +"';",visitor);
+ load(trans,session,creator,"user='"+ user +'\'',visitor);
}
private static void load(Trans trans, Session session, Creator<UserRole> creator, String where, Visitor<UserRole> visitor) {
String query = creator.query(where);
- trans.info().log( "query: " + query );
+ trans.debug().log( "query: " + query );
TimeTaken tt = trans.start("Read UserRoles", Env.REMOTE);
ResultSet results;
@@ -145,7 +145,7 @@ public class UserRole implements Cloneable, CacheChange.Data {
tt.done();
}
} finally {
- trans.info().log("Loaded",totalLoaded,"UserRoles");
+ trans.debug().log("Loaded",totalLoaded,"UserRoles");
}
}
@@ -337,16 +337,26 @@ public class UserRole implements Cloneable, CacheChange.Data {
sb.append("';\n");
}
- public static void batchExtend(StringBuilder sb, List<String> row, String newDate ) {
+ public static void batchExtend(StringBuilder sb, List<String> row, Date newDate ) {
sb.append("UPDATE authz.user_role SET expires='");
- sb.append(newDate);
+ sb.append(Chrono.dateTime(newDate));
sb.append("' WHERE user='");
sb.append(row.get(1));
sb.append("' AND role='");
sb.append(row.get(2));
sb.append("';\n");
}
-
+
+ public void batchUpdateExpires(StringBuilder sb) {
+ sb.append("UPDATE authz.user_role SET expires='");
+ sb.append(Chrono.dateTime(expires()));
+ sb.append("' WHERE user='");
+ sb.append(user());
+ sb.append("' AND role='");
+ sb.append(role());
+ sb.append("';\n");
+ }
+
public static String histMemo(String fmt, List<String> row) {
String reason;
if(row.size()>7) { // Reason included
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Analyze.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Analyze.java
index a0dce749..70c950ec 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Analyze.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Analyze.java
@@ -38,6 +38,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
+import java.util.TreeSet;
import java.util.UUID;
import org.onap.aaf.auth.batch.Batch;
@@ -49,6 +50,7 @@ import org.onap.aaf.auth.batch.helpers.Cred.Instance;
import org.onap.aaf.auth.batch.helpers.ExpireRange;
import org.onap.aaf.auth.batch.helpers.ExpireRange.Range;
import org.onap.aaf.auth.batch.helpers.Future;
+import org.onap.aaf.auth.batch.helpers.LastNotified;
import org.onap.aaf.auth.batch.helpers.Role;
import org.onap.aaf.auth.batch.helpers.UserRole;
import org.onap.aaf.auth.batch.helpers.X509;
@@ -87,6 +89,8 @@ public class Analyze extends Batch {
private CSV.Writer deleteCW;
private CSV.Writer approveCW;
private CSV.Writer extendCW;
+ private Range futureRange;
+ private final String sdate;
public Analyze(AuthzTrans trans) throws APIException, IOException, OrganizationException {
super(trans.env());
@@ -110,14 +114,14 @@ public class Analyze extends Batch {
writerList = new HashMap<>();
expireRange = new ExpireRange(trans.env().access());
- String sdate = Chrono.dateOnlyStamp(expireRange.now);
+ sdate = Chrono.dateOnlyStamp(ExpireRange.now);
for( List<Range> lr : expireRange.ranges.values()) {
for(Range r : lr ) {
if(writerList.get(r.name())==null) {
File file = new File(logDir(),r.name() + sdate +CSV);
CSV csv = new CSV(env.access(),file);
CSV.Writer cw = csv.writer(false);
- cw.row(INFO,r.name(),Chrono.dateOnlyStamp(expireRange.now),r.reportingLevel());
+ cw.row(INFO,r.name(),sdate,r.reportingLevel());
writerList.put(r.name(),cw);
if("Delete".equals(r.name())) {
deleteDate = r.getEnd();
@@ -129,17 +133,18 @@ public class Analyze extends Batch {
}
// Setup New Approvals file
+ futureRange = ExpireRange.newFutureRange();
File file = new File(logDir(),APPROVALS + sdate +CSV);
CSV approveCSV = new CSV(env.access(),file);
approveCW = approveCSV.writer();
- approveCW.row(INFO,APPROVALS,Chrono.dateOnlyStamp(expireRange.now),1);
+ approveCW.row(INFO,APPROVALS,sdate,1);
writerList.put(APPROVALS,approveCW);
// Setup Extend Approvals file
file = new File(logDir(),EXTEND + sdate +CSV);
CSV extendCSV = new CSV(env.access(),file);
extendCW = extendCSV.writer();
- extendCW.row(INFO,EXTEND,Chrono.dateOnlyStamp(expireRange.now),1);
+ extendCW.row(INFO,EXTEND,sdate,1);
writerList.put(EXTEND,extendCW);
// Load full data of the following
@@ -160,7 +165,7 @@ public class Analyze extends Batch {
try {
Future.load(noAvg, session, Future.withConstruct, fut -> {
List<Approval> appls = Approval.byTicket.get(fut.id());
- if(fut.expires().before(expireRange.now)) {
+ if(!futureRange.inRange(fut.expires())) {
deleteCW.comment("Future %s expired", fut.id());
Future.row(deleteCW,fut);
if(appls!=null) {
@@ -179,6 +184,7 @@ public class Analyze extends Batch {
tt.done();
}
+ Set<String> approvers = new TreeSet<>();
tt = trans.start("Connect Approvals with Futures",Trans.SUB);
try {
for(Approval appr : Approval.list) {
@@ -192,6 +198,7 @@ public class Analyze extends Batch {
Approval.row(deleteCW, appr);
} else {
ticket.approvals.add(appr); // add to found Ticket
+ approvers.add(appr.getApprover());
}
}
} finally {
@@ -205,78 +212,98 @@ public class Analyze extends Batch {
Map<String,Pending> pendingApprs = new HashMap<>();
Map<String,Pending> pendingTemp = new HashMap<>();
+ // Convert Good Tickets to keyed User/Role for UserRole Step
+ Map<String,Ticket> mur = new TreeMap<>();
+ LastNotified ln = new LastNotified(session);
+ ln.add(approvers);
+ String approver;
+
tt = trans.start("Analyze Good Tickets",Trans.SUB);
try {
for(Ticket ticket : goodTickets.values()) {
- pendingTemp.clear();
- switch(ticket.f.target()) {
- case "user_role":
- int state[][] = new int[3][3];
- int type;
-
- for(Approval appr : ticket.approvals) {
- switch(appr.getType()) {
- case "owner":
- type=owner;
- break;
- case "supervisor":
- type=supervisor;
- break;
- default:
- type=0;
+ try {
+ pendingTemp.clear();
+ switch(ticket.f.target()) {
+ case "user_role":
+ int state[][] = new int[3][3];
+ int type;
+
+ for(Approval appr : ticket.approvals) {
+ switch(appr.getType()) {
+ case "owner":
+ type=owner;
+ break;
+ case "supervisor":
+ type=supervisor;
+ break;
+ default:
+ type=0;
+ }
+ ++state[type][total]; // count per type
+ switch(appr.getStatus()) {
+ case "pending":
+ ++state[type][pending];
+ approver = appr.getApprover();
+ Pending n = pendingTemp.get(approver);
+ if(n==null) {
+ Date lastNotified = ln.lastNotified(approver,"ur",ticket.f.fdd.target_key);
+ pendingTemp.put(approver,new Pending(lastNotified));
+ } else {
+ n.inc();
+ }
+ break;
+ case "approved":
+ ++state[type][approved];
+ break;
+ default:
+ ++state[type][unknown];
+ }
}
- ++state[type][total]; // count per type
- switch(appr.getStatus()) {
- case "pending":
- ++state[type][pending];
- Pending n = pendingTemp.get(appr.getApprover());
- if(n==null) {
- pendingTemp.put(appr.getApprover(),new Pending());
- } else {
- n.inc();
+
+ // To Approve:
+ // Always must have at least 1 owner
+ if((state[owner][total]>0 && state[owner][approved]>0) &&
+ // If there are no Supervisors, that's ok
+ (state[supervisor][total]==0 ||
+ // But if there is a Supervisor, they must have approved
+ (state[supervisor][approved]>0))) {
+ UserRoleDAO.Data urdd = new UserRoleDAO.Data();
+ try {
+ urdd.reconstitute(ticket.f.fdd.construct);
+ if(urdd.expires.before(ticket.f.expires())) {
+ extendCW.row("extend_ur",urdd.user,urdd.role,ticket.f.expires());
+ }
+ } catch (IOException e) {
+ trans.error().log("Could not reconstitute UserRole");
}
- break;
- case "approved":
- ++state[type][approved];
- break;
- default:
- ++state[type][unknown];
- }
- }
-
- // To Approve:
- // Always must have at least 1 owner
- if((state[owner][total]>0 && state[owner][approved]>0) &&
- // If there are no Supervisors, that's ok
- (state[supervisor][total]==0 ||
- // But if there is a Supervisor, they must have approved
- (state[supervisor][approved]>0))) {
- UserRoleDAO.Data urdd = new UserRoleDAO.Data();
- try {
- urdd.reconstitute(ticket.f.fdd.construct);
- if(urdd.expires.before(ticket.f.expires())) {
- extendCW.row("extend_ur",urdd.user,urdd.role,ticket.f.expires());
+ } else { // Load all the Pending.
+ for(Entry<String, Pending> es : pendingTemp.entrySet()) {
+ Pending p = pendingApprs.get(es.getKey());
+ if(p==null) {
+ pendingApprs.put(es.getKey(), es.getValue());
+ } else {
+ p.inc(es.getValue());
}
- } catch (IOException e) {
- trans.error().log("Could not reconstitute UserRole");
- }
- } else { // Load all the Pending.
- for(Entry<String, Pending> es : pendingTemp.entrySet()) {
- Pending p = pendingApprs.get(es.getKey());
- if(p==null) {
- pendingApprs.put(es.getKey(), es.getValue());
- } else {
- p.inc(es.getValue());
}
}
+ break;
+ }
+ } finally {
+ if("user_role".equals(ticket.f.fdd.target)) {
+ String key = ticket.f.fdd.target_key;
+ if(key!=null) {
+ mur.put(key, ticket);
}
- break;
+ }
}
}
} finally {
tt.done();
}
-
+
+ // Good Tickets no longer needed
+ goodTickets.clear();
+
/**
* Decide to Notify about Approvals, based on activity/last Notified
*/
@@ -288,7 +315,9 @@ public class Analyze extends Batch {
for(Entry<String, Pending> es : pendingApprs.entrySet()) {
Pending p = es.getValue();
- if(p.newApprovals() || p.earliest() == null || p.earliest().after(remind)) {
+ if(p.newApprovals()
+ || p.earliest() == null
+ || p.earliest().after(remind)) {
p.row(approveCW,es.getKey());
}
}
@@ -297,7 +326,6 @@ public class Analyze extends Batch {
}
// clear out Approval Intermediates
- goodTickets.clear();
pendingTemp = null;
pendingApprs = null;
@@ -309,7 +337,7 @@ public class Analyze extends Batch {
try {
tt = trans.start("Analyze UserRoles, storing Owners",Trans.SUB);
Set<String> specialCommented = new HashSet<>();
- Map<String, Set<UserRole>> owners = new TreeMap<String, Set<UserRole>>();
+ Map<String, Set<UserRole>> owners = new TreeMap<>();
try {
UserRole.load(noAvg, session, UserRole.v2_0_11, ur -> {
Identity identity;
@@ -340,20 +368,25 @@ public class Analyze extends Batch {
ur.row(deleteCW, UserRole.UR,String.format("Role %s does not exist", ur.role()));
return;
}
- // Cannot just delete owners, unless there is at least one left. Process later
- if ("owner".equals(ur.rname())) {
- Set<UserRole> urs = owners.get(ur.role());
- if (urs == null) {
- urs = new HashSet<UserRole>();
- owners.put(ur.role(), urs);
- }
- urs.add(ur);
- } else {
- Range r = writeAnalysis(noAvg,ur);
- if(r!=null) {
- Approval existing = findApproval(ur);
- if(existing==null) {
- ur.row(approveCW,UserRole.APPROVE_UR);
+ // Just let expired UserRoles sit until deleted
+ if(futureRange.inRange(ur.expires())) {
+ if(!mur.containsKey(ur.user() + '|' + ur.role())) {
+ // Cannot just delete owners, unless there is at least one left. Process later
+ if ("owner".equals(ur.rname())) {
+ Set<UserRole> urs = owners.get(ur.role());
+ if (urs == null) {
+ urs = new HashSet<UserRole>();
+ owners.put(ur.role(), urs);
+ }
+ urs.add(ur);
+ } else {
+ Range r = writeAnalysis(noAvg,ur);
+ if(r!=null) {
+ Approval existing = findApproval(ur);
+ if(existing==null) {
+ ur.row(approveCW,UserRole.APPROVE_UR);
+ }
+ }
}
}
}
@@ -374,16 +407,16 @@ public class Analyze extends Batch {
tt = trans.start("Analyze Owners Separately",Trans.SUB);
try {
if (!owners.values().isEmpty()) {
- File file = new File(logDir(), EXPIRED_OWNERS + Chrono.dateOnlyStamp(expireRange.now) + CSV);
+ File file = new File(logDir(), EXPIRED_OWNERS + sdate + CSV);
final CSV ownerCSV = new CSV(env.access(),file);
CSV.Writer expOwner = ownerCSV.writer();
- expOwner.row(INFO,EXPIRED_OWNERS,Chrono.dateOnlyStamp(expireRange.now),2);
+ expOwner.row(INFO,EXPIRED_OWNERS,sdate,2);
try {
for (Set<UserRole> sur : owners.values()) {
int goodOwners = 0;
for (UserRole ur : sur) {
- if (ur.expires().after(expireRange.now)) {
+ if (ur.expires().after(ExpireRange.now)) {
++goodOwners;
}
}
@@ -462,7 +495,6 @@ public class Analyze extends Batch {
} catch (CertificateException | IOException e) {
noAvg.error().log(e, "Error Decrypting X509");
}
-
});
} finally {
tt.done();
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Notify.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Notify.java
index 1c1f660c..7cddea2d 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Notify.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Notify.java
@@ -25,22 +25,36 @@ import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
+import java.text.ParseException;
import java.util.ArrayList;
+import java.util.Date;
+import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
+import java.util.TreeMap;
import org.onap.aaf.auth.batch.Batch;
+import org.onap.aaf.auth.batch.approvalsets.Pending;
+import org.onap.aaf.auth.batch.helpers.CQLBatch;
+import org.onap.aaf.auth.batch.helpers.CQLBatchLoop;
+import org.onap.aaf.auth.batch.helpers.LastNotified;
import org.onap.aaf.auth.batch.reports.bodies.NotifyBody;
+import org.onap.aaf.auth.batch.reports.bodies.NotifyPendingApprBody;
import org.onap.aaf.auth.env.AuthzTrans;
import org.onap.aaf.auth.org.Mailer;
import org.onap.aaf.auth.org.Organization.Identity;
import org.onap.aaf.auth.org.OrganizationException;
import org.onap.aaf.cadi.Access;
import org.onap.aaf.cadi.CadiException;
+import org.onap.aaf.cadi.PropAccess;
import org.onap.aaf.cadi.client.Holder;
import org.onap.aaf.cadi.util.CSV;
import org.onap.aaf.misc.env.APIException;
+import org.onap.aaf.misc.env.TimeTaken;
+import org.onap.aaf.misc.env.Trans;
import org.onap.aaf.misc.env.util.Chrono;
public class Notify extends Batch {
@@ -52,9 +66,15 @@ import org.onap.aaf.misc.env.util.Chrono;
private final int indent;
private final boolean urgent;
public final String guiURL;
+ private PropAccess access;
+ private AuthzTrans noAvg;
+ private CQLBatch cqlBatch;
public Notify(AuthzTrans trans) throws APIException, IOException, OrganizationException {
super(trans.env());
+ access = env.access();
+ session = super.cluster.connect();
+
String mailerCls = env.getProperty("MAILER");
String mailFrom = env.getProperty("MAIL_FROM");
String header_html = env.getProperty("HEADER_HTML");
@@ -82,12 +102,14 @@ import org.onap.aaf.misc.env.util.Chrono;
sb.append('\n');
}
String html_css = env.getProperty(HTML_CSS);
+ String temp;
int hc = sb.indexOf(HTML_CSS);
if(hc!=0 && html_css!=null) {
- header = sb.replace(hc,hc+HTML_CSS.length(), html_css).toString();
+ temp = sb.replace(hc,hc+HTML_CSS.length(), html_css).toString();
} else {
- header = sb.toString();
+ temp = sb.toString();
}
+ header = temp.replace("AAF:ENV", batchEnv);
} finally {
br.close();
}
@@ -119,6 +141,8 @@ import org.onap.aaf.misc.env.util.Chrono;
br.close();
}
+ noAvg = trans.env().newTransNoAvg();
+ cqlBatch = new CQLBatch(noAvg.info(),session);
}
/*
@@ -127,10 +151,10 @@ import org.onap.aaf.misc.env.util.Chrono;
*/
@Override
protected void run(AuthzTrans trans) {
- AuthzTrans noAvg = trans.env().newTransNoAvg();
final Holder<List<String>> info = new Holder<>(null);
final Set<String> errorSet = new HashSet<>();
+ String fmt = "%s"+Chrono.dateOnlyStamp()+".csv";
try {
// Class Load possible data
@@ -145,7 +169,6 @@ import org.onap.aaf.misc.env.util.Chrono;
notifyFile.add(new File(logDir, args()[i]));
}
} else {
- String fmt = "%s"+Chrono.dateOnlyStamp()+".csv";
File file;
for(NotifyBody nb : NotifyBody.getAll()) {
file = new File(logDir,String.format(fmt, nb.name()));
@@ -188,6 +211,100 @@ import org.onap.aaf.misc.env.util.Chrono;
for(NotifyBody nb : NotifyBody.getAll()) {
notify(noAvg, nb);
}
+
+ //
+ // Do Pending Approval Notifies. We do this separately, because we are consolidating
+ // all the new entries, etc.
+ //
+ List<CSV> csvList = new ArrayList<>();
+ for(String s : new String[] {"Approvals","ApprovalsNew"}) {
+ File f = new File(logDir(),String.format(fmt, s));
+ if(f.exists()) {
+ csvList.add(new CSV(access,f));
+ }
+ }
+
+ Map<String,Pending> mpending = new TreeMap<>();
+ Holder<Integer> count = new Holder<>(0);
+ for(CSV approveCSV : csvList) {
+ TimeTaken tt = trans.start("Load Analyzed Reminders",Trans.SUB,approveCSV.name());
+ try {
+ approveCSV.visit(row -> {
+ switch(row.get(0)) {
+// case "info":
+// break;
+ case Pending.REMIND:
+ try {
+ String user = row.get(1);
+ Pending p = new Pending(row);
+ Pending mp = mpending.get(user);
+ if(mp==null) {
+ mpending.put(user, p);
+ } else {
+ mp.inc(p); // FYI, unlikely
+ }
+ count.set(count.get()+1);
+ } catch (ParseException e) {
+ trans.error().log(e);
+ }
+ break;
+ }
+ });
+ } catch (IOException | CadiException e) {
+ e.printStackTrace();
+ // .... but continue with next row
+ } finally {
+ tt.done();
+ }
+ }
+ trans.info().printf("Read %d Reminder Rows", count.get());
+
+ NotifyPendingApprBody npab = new NotifyPendingApprBody(access);
+
+ GregorianCalendar gc = new GregorianCalendar();
+ gc.add(GregorianCalendar.DAY_OF_MONTH, 7); // Get from INFO?
+ Date oneWeek = gc.getTime();
+ CSV.Saver rs = new CSV.Saver();
+
+ TimeTaken tt = trans.start("Obtain Last Notifications for Approvers", Trans.SUB);
+ LastNotified lastN;
+ try {
+ lastN = new LastNotified(session);
+ lastN.add(mpending.keySet());
+ } finally {
+ tt.done();
+ }
+
+ Pending p;
+ final CQLBatchLoop cbl = new CQLBatchLoop(cqlBatch,50,dryRun);
+ tt = trans.start("Notify for Pending", Trans.SUB);
+ try {
+ for(Entry<String, Pending> es : mpending.entrySet()) {
+ p = es.getValue();
+ boolean nap = p.newApprovals();
+ if(!nap) {
+ Date dateLastNotified = lastN.lastNotified(es.getKey(),"pending","");
+ if(dateLastNotified==null || dateLastNotified.after(oneWeek) ) {
+ nap=true;
+ }
+ }
+ if(nap) {
+ rs.row("appr", es.getKey(),p.qty(),batchEnv);
+ npab.store(rs.asList());
+ if(notify(noAvg, npab)>0) {
+ // Update
+ cbl.preLoop();
+ lastN.update(cbl.inc(),es.getKey(),"pending","");
+ }
+ }
+ }
+ } finally {
+ cbl.flush();
+ tt.done();
+ }
+ trans.info().printf("Created %d Notifications", count.get());
+
+
} catch (APIException | IOException e1) {
trans.error().log(e1);
@@ -272,8 +389,4 @@ import org.onap.aaf.misc.env.util.Chrono;
return nb.count();
}
- @Override
- protected void _close(AuthzTrans trans) {
- }
-
}
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/NotifyCredBody.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/NotifyCredBody.java
index 94502d9f..15a104d1 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/NotifyCredBody.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/NotifyCredBody.java
@@ -32,15 +32,17 @@ import org.onap.aaf.misc.env.util.Chrono;
public abstract class NotifyCredBody extends NotifyBody {
private final String explanation;
+ private final String instruction;
+
public NotifyCredBody(Access access, String name) throws IOException {
super(access,"cred",name);
// Default
explanation = "The following Credentials that you are responsible for "
- + "are expiring on the dates shown. "
- + "Failure to act before the expiration date will cause your App's "
- + "Authentications to fail."
- + "<h3>Instructions for 'Password':</h3><ul>"
+ + "are expiring on the dates shown. <br><br>"
+ ;
+
+ instruction = "<br><h3>Instructions for 'Password':</h3><ul>"
+ "<li><b><i>Click</i></b> on the Fully Qualified ID to ADD a new Password</li>"
+ "<li><b>REMEMBER!</b> You are not finished until you <ol>"
+ "<li><b>CHANGE <i>ALL</i></b> the configurations on <b><i>ALL</i></b> your processes!!</li>"
@@ -48,10 +50,20 @@ public abstract class NotifyCredBody extends NotifyBody {
+ "<li>IF there is a WARNING, click the link for more information</li>"
+ "</ul>";
}
+
+ /**
+ * Default Dynamic Text. Override is expected
+ * @return
+ */
+ protected String dynamic() {
+ return "Failure to act before the expiration date will cause your App's Authentications to fail.";
+ }
@Override
public boolean body(AuthzTrans trans, StringBuilder sb, int indent, Notify n, String id) {
- println(sb,indent,explanation);
+ print(sb,indent,explanation);
+ print(sb,indent,dynamic());
+ println(sb,indent,instruction);
println(sb,indent,"<table>");
indent+=2;
println(sb,indent,"<tr>");
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/OneMonthNotifyCredBody.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/OneMonthNotifyCredBody.java
index a288f495..866dc23c 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/OneMonthNotifyCredBody.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/OneMonthNotifyCredBody.java
@@ -34,4 +34,12 @@ public class OneMonthNotifyCredBody extends NotifyCredBody {
public String subject() {
return String.format("AAF One Month Credential Notification (ENV: %s)",env);
}
+
+ /* (non-Javadoc)
+ * @see org.onap.aaf.auth.batch.reports.bodies.NotifyCredBody#dynamic()
+ */
+ @Override
+ protected String dynamic() {
+ return "This is your <b>one month</b> notification. " + super.dynamic();
+ }
}
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/TwoMonthNotifyCredBody.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/TwoMonthNotifyCredBody.java
index eb52c6d0..98ee47d9 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/TwoMonthNotifyCredBody.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/TwoMonthNotifyCredBody.java
@@ -34,4 +34,14 @@ public class TwoMonthNotifyCredBody extends NotifyCredBody {
public String subject() {
return String.format("AAF Two Month Credential Notification (ENV: %s)",env);
}
+
+ /* (non-Javadoc)
+ * @see org.onap.aaf.auth.batch.reports.bodies.NotifyCredBody#dynamic()
+ */
+ @Override
+ protected String dynamic() {
+ return "This is a friendly, <b>2 month reminder</b> to schedule appropriate creation and deployment "
+ + "of your credentials, and modification of your configurations on a per instance basis. "
+ + " Use the following text to help create your Ticket.";
+ }
}
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/TwoWeeksNotifyCredBody.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/TwoWeeksNotifyCredBody.java
index 818556c7..46188568 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/TwoWeeksNotifyCredBody.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/TwoWeeksNotifyCredBody.java
@@ -34,4 +34,12 @@ public class TwoWeeksNotifyCredBody extends NotifyCredBody {
public String subject() {
return String.format("AAF Two Week Credential Notification (ENV: %s)",env);
}
+
+ /* (non-Javadoc)
+ * @see org.onap.aaf.auth.batch.reports.bodies.NotifyCredBody#dynamic()
+ */
+ @Override
+ protected String dynamic() {
+ return "You have now reached critical stage. This email is escalated to your superiors. " + super.dynamic();
+ }
}
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/update/Approvals.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/update/Approvals.java
index 03c812ae..0df49343 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/update/Approvals.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/update/Approvals.java
@@ -23,13 +23,12 @@ package org.onap.aaf.auth.batch.update;
import java.io.File;
import java.io.IOException;
-import java.text.ParseException;
import java.util.ArrayList;
-import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Set;
import java.util.TreeMap;
import org.onap.aaf.auth.batch.Batch;
@@ -38,42 +37,34 @@ import org.onap.aaf.auth.batch.approvalsets.ApprovalSet;
import org.onap.aaf.auth.batch.approvalsets.Pending;
import org.onap.aaf.auth.batch.approvalsets.URApprovalSet;
import org.onap.aaf.auth.batch.helpers.BatchDataView;
-import org.onap.aaf.auth.batch.helpers.CQLBatch;
-import org.onap.aaf.auth.batch.helpers.CQLBatchLoop;
-import org.onap.aaf.auth.batch.helpers.LastNotified;
import org.onap.aaf.auth.batch.helpers.NS;
-import org.onap.aaf.auth.batch.helpers.Notification;
-import org.onap.aaf.auth.batch.helpers.Notification.TYPE;
import org.onap.aaf.auth.batch.helpers.Role;
import org.onap.aaf.auth.batch.helpers.UserRole;
-import org.onap.aaf.auth.batch.reports.Notify;
-import org.onap.aaf.auth.batch.reports.bodies.NotifyPendingApprBody;
import org.onap.aaf.auth.dao.cass.UserRoleDAO;
import org.onap.aaf.auth.env.AuthzTrans;
import org.onap.aaf.auth.layer.Result;
import org.onap.aaf.auth.org.OrganizationException;
-import org.onap.aaf.cadi.Access;
import org.onap.aaf.cadi.CadiException;
import org.onap.aaf.cadi.client.Holder;
import org.onap.aaf.cadi.util.CSV;
+import org.onap.aaf.cadi.util.CSV.Writer;
import org.onap.aaf.misc.env.APIException;
import org.onap.aaf.misc.env.TimeTaken;
import org.onap.aaf.misc.env.Trans;
import org.onap.aaf.misc.env.util.Chrono;
public class Approvals extends Batch {
- private final Access access;
private final AuthzTrans noAvg;
private BatchDataView dataview;
private List<CSV> csvList;
- private GregorianCalendar now;
- private final Notify notify;
+ private Writer napproveCW;
+ private static final GregorianCalendar now = new GregorianCalendar();
+ private static final String sdate = Chrono.dateOnlyStamp(now);
+ private static final String CSV = ".csv";
+ private static final String APPROVALS_NEW = "ApprovalsNew";
-
public Approvals(AuthzTrans trans) throws APIException, IOException, OrganizationException {
super(trans.env());
- notify = new Notify(trans);
- access = env.access();
noAvg = env.newTransNoAvg();
noAvg.setUser(new BatchPrincipal("batch:Approvals"));
session = cluster.connect();
@@ -82,8 +73,6 @@ public class Approvals extends Batch {
Role.load(trans, session);
UserRole.load(trans, session, UserRole.v2_0_11);
- now = new GregorianCalendar();
-
csvList = new ArrayList<>();
File f;
if(args().length>0) {
@@ -103,45 +92,21 @@ public class Approvals extends Batch {
trans.error().printf("CSV File %s does not exist",f.getAbsolutePath());
}
}
+
+
+ File file = new File(logDir(),APPROVALS_NEW + sdate +CSV);
+ CSV approveCSV = new CSV(env.access(),file);
+ napproveCW = approveCSV.writer();
+ napproveCW.row("info",APPROVALS_NEW,sdate,1);
+
}
@Override
protected void run(AuthzTrans trans) {
Map<String,Pending> mpending = new TreeMap<>();
- Holder<Integer> count = new Holder<>(0);
- final CQLBatchLoop cbl = new CQLBatchLoop(new CQLBatch(noAvg.info(),session),100,dryRun);
- for(CSV approveCSV : csvList) {
- TimeTaken tt = trans.start("Load Analyzed Reminders",Trans.SUB,approveCSV.name());
- try {
- approveCSV.visit(row -> {
- switch(row.get(0)) {
- case Pending.REMIND:
- try {
- String user = row.get(1);
- Pending p = new Pending(row);
- Pending mp = mpending.get(user);
- if(mp==null) {
- mpending.put(user, p);
- } else {
- mp.inc(p); // FYI, unlikely
- }
- count.set(count.get()+1);
- } catch (ParseException e) {
- trans.error().log(e);
- }
- break;
- }
- });
- } catch (IOException | CadiException e) {
- e.printStackTrace();
- // .... but continue with next row
- } finally {
- tt.done();
- }
- }
- trans.info().printf("Processed %d Reminder Rows", count.get());
+ Pending p = Pending.create();
- count.set(0);
+ Holder<Integer> count = new Holder<>(0);
for(CSV approveCSV : csvList) {
TimeTaken tt = trans.start("Processing %s's UserRoles",Trans.SUB,approveCSV.name());
try {
@@ -155,14 +120,20 @@ public class Approvals extends Batch {
});
Result<Void> rw = uras.write(noAvg);
if(rw.isOK()) {
- Pending p = new Pending();
- Pending mp = mpending.get(urdd.user);
- if(mp==null) {
- mpending.put(urdd.user, p);
+ Set<String> approvers = uras.approvers();
+ if(approvers.isEmpty()) {
+ trans.error().printf("No Approvers found for %s-%s (probably no owner)",urdd.user,urdd.role);
} else {
- mp.inc(p);
+ for(String approver : approvers) {
+ Pending mp = mpending.get(approver);
+ if(mp==null) {
+ mpending.put(approver, Pending.create());
+ } else {
+ mp.inc(p); // FYI, unlikely
+ }
+ }
+ count.set(count.get()+1);
}
- count.set(count.get()+1);
} else {
trans.error().log(rw.errorString());
}
@@ -178,60 +149,25 @@ public class Approvals extends Batch {
}
trans.info().printf("Processed %d UserRoles", count.get());
- count.set(0);
- NotifyPendingApprBody npab = new NotifyPendingApprBody(access);
-
- GregorianCalendar gc = new GregorianCalendar();
- gc.add(GregorianCalendar.DAY_OF_MONTH, 7);
- Date oneWeek = gc.getTime();
- CSV.Saver rs = new CSV.Saver();
-
- tt = trans.start("Obtain Last Notifications", Trans.SUB);
- LastNotified lastN;
- try {
- lastN = new LastNotified(session);
- lastN.add(mpending.keySet());
- } finally {
- tt.done();
- }
-
- Pending p;
- tt = trans.start("Notify for Pending", Trans.SUB);
+ tt = trans.start("Processing %s's UserRoles",Trans.SUB,approveCSV.name());
+ int cnt = 0;
try {
- for(Entry<String, Pending> es : mpending.entrySet()) {
- p = es.getValue();
- Date dateLastNotified = lastN.lastNotified(es.getKey());
- if(p.newApprovals() || dateLastNotified==null || dateLastNotified.after(oneWeek) ) {
- rs.row("appr", es.getKey(),p.qty(),batchEnv);
- npab.store(rs.asList());
- if(notify.notify(noAvg, npab)>0) {
- // Update
- cbl.preLoop();
- update(cbl.inc(),es.getKey(),Notification.TYPE.OA);
- }
- }
- }
- } finally {
- tt.done();
- }
- trans.info().printf("Created %d Notifications", count.get());
- }
+ for(Entry<String, Pending> es : mpending.entrySet()) {
+ p.row(napproveCW,es.getKey());
+ ++cnt;
+ }
+ } finally {
+ tt.done();
+ trans.info().printf("Processed %d Reminders", cnt);
+ }
+ }
}
-
- private void update(StringBuilder sb, String user, TYPE oa) {
- sb.append("UPDATE authz.notify SET last=dateof(now()) WHERE user='");
- sb.append(user);
- sb.append("' AND type=");
- sb.append(oa.idx());
- sb.append(';');
-
- }
@Override
protected void _close(AuthzTrans trans) {
- if(session!=null) {
- session.close();
- session = null;
- }
+ if(napproveCW!=null) {
+ napproveCW.flush();
+ napproveCW.close();
+ }
}
}
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/update/Extend.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/update/Extend.java
index 870dc1e5..3a0f7b9e 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/update/Extend.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/update/Extend.java
@@ -151,7 +151,7 @@ public class Extend extends Batch {
gc.setTime(now.getTime());
}
gc.add(gcType, extendBy);
- UserRole.batchExtend(sb,row,Chrono.dateTime(gc));
+ UserRole.batchExtend(sb,row,gc.getTime());
break;
case "cred":
int ctype = Integer.parseInt(row.get(3));
diff --git a/auth/auth-batch/src/test/java/org/onap/aaf/auth/batch/test/JU_NotificationTest.java b/auth/auth-batch/src/test/java/org/onap/aaf/auth/batch/test/JU_NotificationTest.java
deleted file mode 100644
index f1cba0b7..00000000
--- a/auth/auth-batch/src/test/java/org/onap/aaf/auth/batch/test/JU_NotificationTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * ============LICENSE_START====================================================
- * org.onap.aaf
- * ===========================================================================
- * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
- * ===========================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END====================================================
- *
- */
-package org.onap.aaf.auth.batch.test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.when;
-import static org.mockito.MockitoAnnotations.initMocks;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.onap.aaf.auth.batch.actions.Message;
-import org.onap.aaf.auth.batch.helpers.Creator;
-import org.onap.aaf.auth.batch.helpers.Notification;
-import org.onap.aaf.auth.batch.helpers.Notification.TYPE;
-import org.onap.aaf.auth.env.AuthzTrans;
-import org.onap.aaf.misc.env.Env;
-import org.onap.aaf.misc.env.LogTarget;
-import org.onap.aaf.misc.env.TimeTaken;
-
-public class JU_NotificationTest {
-
- @Mock
- private AuthzTrans trans;
- @Mock
- private Creator<Notification> creator;
- @Mock
- private TimeTaken tt;
-
- @Mock
- private LogTarget logTarget;
- private Message msg;
-
- @Before
- public void setUp() throws Exception {
- initMocks(this);
-
- msg = new Message();
- msg.line("%n", "Message");
-
- when(trans.info()).thenReturn(logTarget);
- when(trans.start("Load Notify", Env.REMOTE)).thenReturn(tt);
- }
-
- @Test
- public void test() {
- Notification notification = Notification.create("user", TYPE.CN);
- assertEquals(notification.checksum(), 0);
- notification.set(msg);
- assertEquals(notification.checksum(), 10);
- assertNull(Notification.get("user", TYPE.CN));
- assertTrue(notification.update(trans, null, true));
- assertTrue(notification.toString().contains("\"user\",\"CN\","));
-
-
- }
-} \ No newline at end of file
diff --git a/auth/auth-cass/cass_init/init.cql b/auth/auth-cass/cass_init/init.cql
index 75b02c53..f280de00 100644
--- a/auth/auth-cass/cass_init/init.cql
+++ b/auth/auth-cass/cass_init/init.cql
@@ -90,12 +90,12 @@ CREATE TABLE cert (
CREATE INDEX cert_id ON cert(id);
CREATE INDEX cert_x500 ON cert(x500);
-CREATE TABLE notify (
+CREATE TABLE notified (
user text,
- type int,
+ target text,
+ key text,
last timestamp,
- checksum int,
- PRIMARY KEY (user,type)
+ PRIMARY KEY (user,target,key)
);
CREATE TABLE x509 (
@@ -175,6 +175,7 @@ CREATE TABLE future (
);
CREATE INDEX future_idx ON future(target);
CREATE INDEX future_start_idx ON future(start);
+CREATE INDEX future_target_key ON authz.future (target_key);
CREATE TABLE approval (
diff --git a/auth/auth-cass/cass_init/init2_10.cql b/auth/auth-cass/cass_init/init2_10.cql
index b7195076..21bd4b63 100644
--- a/auth/auth-cass/cass_init/init2_10.cql
+++ b/auth/auth-cass/cass_init/init2_10.cql
@@ -2,3 +2,13 @@ use authz;
alter TABLE cred ADD tag varchar;
alter TABLE future ADD target_key varchar;
alter TABLE future ADD target_date timestamp;
+CREATE INDEX future_target_key ON authz.future (target_key);
+
+CREATE TABLE notified (
+ user text,
+ target text,
+ key text,
+ last timestamp,
+ PRIMARY KEY (user,target,key)
+);
+
diff --git a/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/ca/LocalCA.java b/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/ca/LocalCA.java
index 08c96853..c51ddbde 100644
--- a/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/ca/LocalCA.java
+++ b/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/ca/LocalCA.java
@@ -203,7 +203,7 @@ public class LocalCA extends CA {
public X509andChain sign(Trans trans, CSRMeta csrmeta) throws IOException, CertException {
GregorianCalendar gc = new GregorianCalendar();
Date start = gc.getTime();
- gc.add(GregorianCalendar.MONTH, 6);
+ gc.add(GregorianCalendar.MONTH, 12);
Date end = gc.getTime();
X509Certificate x509;
TimeTaken tt = trans.start("Create/Sign Cert",Env.SUB);
diff --git a/auth/auth-gui/src/main/java/org/onap/aaf/auth/gui/pages/ApprovalForm.java b/auth/auth-gui/src/main/java/org/onap/aaf/auth/gui/pages/ApprovalForm.java
index f1730380..0c984e4d 100644
--- a/auth/auth-gui/src/main/java/org/onap/aaf/auth/gui/pages/ApprovalForm.java
+++ b/auth/auth-gui/src/main/java/org/onap/aaf/auth/gui/pages/ApprovalForm.java
@@ -40,13 +40,12 @@ import org.onap.aaf.auth.gui.Table.Cells;
import org.onap.aaf.auth.gui.table.AbsCell;
import org.onap.aaf.auth.gui.table.ButtonCell;
import org.onap.aaf.auth.gui.table.RadioCell;
-import org.onap.aaf.auth.gui.table.RefCell;
import org.onap.aaf.auth.gui.table.TableData;
-import org.onap.aaf.auth.gui.table.TextAndRefCell;
import org.onap.aaf.auth.gui.table.TextCell;
+import org.onap.aaf.auth.gui.table.TextToolTipCell;
import org.onap.aaf.auth.org.Organization;
-import org.onap.aaf.auth.org.OrganizationFactory;
import org.onap.aaf.auth.org.Organization.Identity;
+import org.onap.aaf.auth.org.OrganizationFactory;
import org.onap.aaf.cadi.CadiException;
import org.onap.aaf.cadi.client.Future;
import org.onap.aaf.cadi.client.Rcli;
@@ -91,7 +90,7 @@ public class ApprovalForm extends Page {
}
},
new Form(true,new Table<AAF_GUI,AuthzTrans>("Approval Requests", gui.env.newTransNoAvg(),new Model(gui.env),"class=stdform"))
- .preamble("The following requires your Approval to proceed in the AAF System.</p><p class=subtext>Hover on Identity for Name; click for WebPhone; If Deny is the only option, User is no longer valid."),
+ .preamble("The following requires your Approval to proceed in the AAF System.</p><p class=subtext>Hover on Name for Identity; If Deny is the only option, User is no longer valid."),
new NamedCode(false, "selectAlljs") {
@Override
public void code(final Cache<HTMLGen> cache, final HTMLGen hgen) throws APIException, IOException {
@@ -116,7 +115,7 @@ public class ApprovalForm extends Page {
*/
private static class Model extends TableData<AAF_GUI,AuthzTrans> {
//TODO come up with a generic way to do ILM Info (people page)
- private static final String TODO_ILM_INFO = "TODO: ILM Info";
+// private static final String TODO_ILM_INFO = "TODO: ILM Info";
private static final String[] headers = new String[] {"Identity","Request","Approve","Deny"};
@@ -216,7 +215,7 @@ public class ApprovalForm extends Page {
// } else {
approverHeader = new AbsCell[] {
new TextCell("Approvals Delegated to Me by " + iapprover.fullName()
- + '(' + iapprover.id() +')',
+ + '(' + iapprover.id() + ')',
new String[] {"colspan=4", "class=head"})
};
// }
@@ -242,34 +241,29 @@ public class ApprovalForm extends Page {
userCell = AbsCell.Null;
} else if (user.endsWith(trans.org().getRealm())){
userOK=true;
-// String title;
+ String title;
Organization org = OrganizationFactory.obtain(trans.env(), user);
if (org==null) {
-// title="";
+ title="";
userCell = new TextCell(user);
} else {
Identity au = org.getIdentity(trans, user);
if (au!=null) {
if(au.isPerson()) {
- userCell = new TextCell(au.fullName() + "\n(" + au.id() + ')');
+ userCell = new TextToolTipCell(au.fullName(),"Identity: " + au.id());
} else {
- userCell = new TextCell(au.fullID());
+ Identity managedBy = au.responsibleTo();
+ if (managedBy==null) {
+ title ="Identity: " + au.type();
+ } else {
+ title="Sponsor: " + managedBy.fullName();
+ }
+ userCell = new TextToolTipCell(au.fullID(),title);
}
-//
-// if ("MECHID".equals(au.type())) {
-// Identity managedBy = au.responsibleTo();
-// if (managedBy==null) {
-// title ="title=" + au.type();
-// } else {
-// title="title=Sponsor is " + managedBy.fullName();
-// }
-// } else {
-// title="title=" + au.fullName();
-// }
} else {
userOK=false;
-// title="title=Not a User at " + org.getName();
- userCell = new TextCell(user);
+ title="Not a User at " + org.getName();
+ userCell = new TextToolTipCell(user,title);
}
}
prevUser=user;
diff --git a/auth/auth-gui/src/main/java/org/onap/aaf/auth/gui/table/TextToolTipCell.java b/auth/auth-gui/src/main/java/org/onap/aaf/auth/gui/table/TextToolTipCell.java
new file mode 100644
index 00000000..986b8245
--- /dev/null
+++ b/auth/auth-gui/src/main/java/org/onap/aaf/auth/gui/table/TextToolTipCell.java
@@ -0,0 +1,51 @@
+/**
+ * ============LICENSE_START====================================================
+ * org.onap.aaf
+ * ===========================================================================
+ * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
+ * ===========================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END====================================================
+ *
+ */
+
+package org.onap.aaf.auth.gui.table;
+
+import org.onap.aaf.misc.xgen.html.HTMLGen;
+
+/**
+ * Write Simple Text into a Cell
+ * @author Jonathan
+ *
+ */
+public class TextToolTipCell extends AbsCell {
+ public final String name;
+ private final String[] attrs;
+ private final String tooltip;
+
+ public TextToolTipCell(String name, String tooltip, String... attributes) {
+ attrs = attributes;
+ this.name = name;
+ this.tooltip = "<abbr title=\"" + tooltip + "\">";
+ }
+
+ @Override
+ public void write(HTMLGen hgen) {
+ hgen.text(tooltip + name + "</abbr>");
+ }
+
+ @Override
+ public String[] attrs() {
+ return attrs;
+ }
+}
diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/filter/CadiFilter.java b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/CadiFilter.java
index cd8eff44..01bf6f2e 100644
--- a/cadi/core/src/main/java/org/onap/aaf/cadi/filter/CadiFilter.java
+++ b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/CadiFilter.java
@@ -303,7 +303,14 @@ public class CadiFilter implements Filter {
private boolean noAuthn(HttpServletRequest hreq) {
if (pathExceptions!=null) {
String pi = hreq.getPathInfo();
- if (pi==null) return false; // JBoss sometimes leaves null
+ if (pi==null) {
+ // Attempt to get from URI only (Daniel Rose)
+ pi = hreq.getRequestURI().substring(hreq.getContextPath().length());
+ if(pi==null) {
+ // Nothing works.
+ return false; // JBoss sometimes leaves null
+ }
+ }
for (String pe : pathExceptions) {
if (pi.startsWith(pe))return true;
}
diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/util/CSV.java b/cadi/core/src/main/java/org/onap/aaf/cadi/util/CSV.java
index 1d60ae58..47de84ed 100644
--- a/cadi/core/src/main/java/org/onap/aaf/cadi/util/CSV.java
+++ b/cadi/core/src/main/java/org/onap/aaf/cadi/util/CSV.java
@@ -44,6 +44,7 @@ public class CSV {
private File csv;
private Access access;
private boolean processAll;
+ private char delimiter = ',';
public CSV(Access access, File file) {
this.access = access;
@@ -57,6 +58,11 @@ public class CSV {
processAll = false;
}
+ public CSV setDelimiter(char delimiter) {
+ this.delimiter = delimiter;
+ return this;
+ }
+
public String name() {
return csv.getName();
}
@@ -116,16 +122,17 @@ public class CSV {
escape = true;
}
break;
- case ',':
- if(quotes) {
- sb.append(c);
+ default:
+ if(delimiter==c) {
+ if(quotes) {
+ sb.append(c);
+ } else {
+ row.add(sb.toString());
+ sb.setLength(0);
+ }
} else {
- row.add(sb.toString());
- sb.setLength(0);
+ sb.append(c);
}
- break;
- default:
- sb.append(c);
}
}
if(sb.length()>0 || c==',') {