Add Cred Reporting Mailer

Issue-ID: AAF-740,AAF-753,AAF-754
Change-Id: If2efc6ffbfa9897581ea00eb148fa61d793b058e
Signed-off-by: Instrumental <jonathan.gathman@att.com>
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 543564d..1c65c05 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
@@ -90,6 +90,7 @@
     public static final String GUI_URL="GUI_URL";
     
     protected final Organization org;
+	protected String version;
     
     protected Batch(AuthzEnv env) throws APIException, IOException, OrganizationException {
         if (batchEnv != null) {
@@ -143,6 +144,8 @@
                 }
             }
         }
+        
+        version = env.getProperty(VERSION,Config.AAF_DEFAULT_API_VERSION);
     }
 
     protected abstract void run(AuthzTrans trans);
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 b6767d4..e1c75bf 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
@@ -55,7 +55,7 @@
 		}
 		Result<NsDAO.Data> n = dv.ns(trans, urdd.ns);
 		if(n.notOKorIsEmpty()) {
-			throw new CadiException(String.format("Namespace '%s' does not exist: %s", urdd.ns));
+			throw new CadiException(String.format("Namespace '%s' does not exist: %s", urdd.ns,r.details));
 		}
 		UserRoleDAO.Data found = null;
 		Result<List<Data>> lur = dv.ursByRole(trans, urdd.role);
@@ -68,7 +68,7 @@
 			}
 		}
 		if(found==null) {
-			throw new CadiException(String.format("User '%s' in Role '%s' does not exist: %s", urdd.user,urdd.role));
+			throw new CadiException(String.format("User '%s' in Role '%s' does not exist: %s", urdd.user,urdd.role,r.details));
 		}
 		
 		// Primarily, Owners are responsible, unless it's owned by self
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Cred.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Cred.java
index c4a9b0d..8a5dfea 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Cred.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Cred.java
@@ -62,16 +62,23 @@
         public final int type;
         public final Date expires,written;
         public final Integer other;
+        public final String tag;
+        public final Integer attn;
+        public final String notes;
+
         
-        public Instance(int type, Date expires, Integer other, long written) {
+        public Instance(int type, Date expires, Integer other, long written, String tag, int attn, String notes) {
             this.type = type;
             this.expires = expires;
             this.other = other;
             this.written = new Date(written);
+            this.tag = tag;
+            this.attn = attn;
+            this.notes = notes;
         }
         
         public String toString() {
-        	return expires.toString() + ": " + type;
+        	return expires.toString() + ": " + type + ' ' + notes;
         }
     }
     
@@ -107,12 +114,12 @@
     }
 
     public static void load(Trans trans, Session session, int ... types ) {
-        load(trans, session,"select id, type, expires, other, writetime(cred) from authz.cred;",types);
+        load(trans, session,"select id, type, expires, other, writetime(cred), tag, attn, notes from authz.cred;",types);
         
     }
 
     public static void loadOneNS(Trans trans, Session session, String ns,int ... types ) {
-        load(trans, session,"select id, type, expires, other, writetime(cred) from authz.cred WHERE ns='" + ns + "';");
+        load(trans, session,"select id, type, expires, other, writetime(cred), tag, attn, notes from authz.cred WHERE ns='" + ns + "';");
     }
 
     private static void load(Trans trans, Session session, String query, int ...types) {
@@ -149,7 +156,8 @@
                             continue;
                         }
                     }
-                    add(row.getString(0), row.getInt(1),row.getTimestamp(2),row.getInt(3),row.getLong(4));
+                    add(row.getString(0), row.getInt(1),row.getTimestamp(2),row.getInt(3),row.getLong(4),
+                    		row.getString(5),row.getInt(6),row.getString(7));
                 }
             } finally {
                 tt.done();
@@ -164,14 +172,17 @@
     		final int type,
     		final Date timestamp,
     		final int other,
-    		final long written
+    		final long written,
+    		final String tag,
+    		final int attn,
+    		final String notes
     		) {
         Cred cred = data.get(id);
         if (cred==null) {
             cred = new Cred(id);
             data.put(id, cred);
         }
-        cred.instances.add(new Instance(type, timestamp, other, written/1000));
+        cred.instances.add(new Instance(type, timestamp, other, written/1000,tag,attn,notes));
         
         List<Cred> lscd = byNS.get(cred.ns);
         if (lscd==null) {
@@ -277,7 +288,8 @@
     }
     
     public void row(final CSV.Writer csvw, final Instance inst) {
-    	csvw.row("cred",id,ns,Integer.toString(inst.type),Chrono.dateOnlyStamp(inst.expires),inst.expires.getTime());
+    	csvw.row("cred",id,ns,Integer.toString(inst.type),Chrono.dateOnlyStamp(inst.expires),
+    			inst.expires.getTime(),inst.tag,inst.attn,inst.notes);
     }
 
 
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 f8d9888..7fd2674 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
@@ -21,7 +21,6 @@
 
 import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileReader;
 import java.io.IOException;
 import java.lang.reflect.Constructor;
@@ -42,22 +41,29 @@
 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.util.Chrono;
 
 public class Notify extends Batch {
+	private static final String HTML_CSS = "HTML_CSS";
 	private final Mailer mailer;
-	private final String mailFrom;
 	private final String header;
 	private final String footer;
 	private List<File> notifyFile;
+	public final String guiURL;
+	private int maxEmails;
+	private int indent;
 
 	public Notify(AuthzTrans trans) throws APIException, IOException, OrganizationException {
 		super(trans.env());
 		String mailerCls = env.getProperty("MAILER");
-		mailFrom = env.getProperty("MAIL_FROM");
+		String mailFrom = env.getProperty("MAIL_FROM");
 		String header_html = env.getProperty("HEADER_HTML");
 		String footer_html = env.getProperty("FOOTER_HTML");
-		if(mailerCls==null || mailFrom==null || header_html==null || footer_html==null) {
-			throw new APIException("Notify requires MAILER, MAILER_FROM, HEADER_HTML and FOOTER_HTML properties");
+		String maxEmails = env.getProperty("MAX_EMAIL");
+		guiURL = env.getProperty("GUI_URL");
+		this.maxEmails = maxEmails==null?1:Integer.parseInt(maxEmails);
+		if(mailerCls==null || mailFrom==null || guiURL==null || header_html==null || footer_html==null) {
+			throw new APIException("Notify requires MAILER, MAILER_FROM, GUI_URL, HEADER_HTML and FOOTER_HTML properties");
 		}
 		try {
 			Class<?> mailc = Class.forName(mailerCls);
@@ -75,11 +81,32 @@
 				sb.append(line);
 				sb.append('\n');
 			}
-			header = sb.toString();
+			String html_css = env.getProperty(HTML_CSS);
+			int hc = sb.indexOf(HTML_CSS);
+			if(hc!=0 && html_css!=null) {
+				header = sb.replace(hc,hc+HTML_CSS.length(), html_css).toString();
+			} else {
+				header = sb.toString();
+			}
 		} finally {
 			br.close();
 		}
 		
+		
+		
+		// Establish index from header
+		int lastTag = header.lastIndexOf('<');
+		if(lastTag>0) {
+			int prevCR = header.lastIndexOf('\n',lastTag);
+			if(prevCR>0) {
+				indent = lastTag-prevCR;
+			} else {
+				indent = 6; //arbitrary
+			}
+		}
+
+		
+		sb.setLength(0);
 		br = new BufferedReader(new FileReader(footer_html));
 		try {
 			while((line=br.readLine())!=null) {
@@ -101,6 +128,18 @@
         	for(int i=0;i<args().length;++i) {
         		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()));
+        		if(file.exists()) {
+        			trans.info().printf("Processing %s",file.getCanonicalPath());
+        			notifyFile.add(file);
+        		} else {
+        			trans.info().printf("No Files found for %s",nb.name());
+        		}
+        	}
         }
 	}
 
@@ -119,6 +158,7 @@
 		final Set<String> errorSet = new HashSet<>();
 		
 		try {
+			EMAILTYPE:
 			for(File f : notifyFile) {
 				CSV csv = new CSV(env.access(),f);
 				try {
@@ -150,7 +190,6 @@
 						toList.clear();
 						ccList.clear();
 						try {
-							String bodyS = nb.body(noAvg, notify, id);
 							Identity identity = trans.org().getIdentity(noAvg, id);
 							if(!identity.isPerson()) {
 								identity = identity.responsibleTo();
@@ -165,11 +204,19 @@
 									}
 								}
 							}
+							
+							StringBuilder content = new StringBuilder();
+							content.append(String.format(header,version,Identity.mixedCase(identity.firstName())));
+							
+							nb.body(noAvg, content, indent, notify, id);
+							content.append(footer);
 								
-							mailer.sendEmail(noAvg, dryRun, mailFrom, toList, ccList, subject, 
-									String.format(header,"2.1.9",Identity.mixedCase(identity.firstName()))+
-									bodyS +
-									footer, urgent);
+							if(!mailer.sendEmail(noAvg, dryRun, toList, ccList, subject,content.toString(), urgent)) {
+								trans.error().log("Mailer failed to send Mail");
+							}
+							if(maxEmails>0 && mailer.count()>=maxEmails) {
+								break EMAILTYPE;
+							}
 						} catch (OrganizationException e) {
 							trans.error().log(e);
 						}
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/AbsCredBody.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/AbsCredBody.java
deleted file mode 100644
index 6dd5bb2..0000000
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/AbsCredBody.java
+++ /dev/null
@@ -1,40 +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.reports.bodies;
-
-import java.util.List;
-
-public abstract class AbsCredBody extends NotifyBody {
-
-	public AbsCredBody(final String name) {
-		super("cred",name);
-	}
-
-	@Override
-	public String user(List<String> row) {
-		if( (row != null) && row.size()>1) {
-			return row.get(1);
-		}
-		return null;
-	}
-}
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/NotifyBody.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/NotifyBody.java
index 429ea6d..453c2f2 100644
--- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/NotifyBody.java
+++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/bodies/NotifyBody.java
@@ -30,6 +30,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -41,6 +42,7 @@
 import org.onap.aaf.misc.env.APIException;
 
 public abstract class NotifyBody {
+	private static final String DUPL = "<td style=\"text-indent: 4em;\">''</td>";
 	private static final Map<String,NotifyBody> bodyMap = new HashMap<>();
 
 	protected Map<String,List<List<String>>> rows;
@@ -105,7 +107,7 @@
 	 * @param row
 	 * @return
 	 */
-	public abstract String body(AuthzTrans trans, Notify n, String id);
+	public abstract boolean body(AuthzTrans trans, StringBuilder sb, int indent, Notify n, String id);
 	
 	/**
 	 * Return "null" if user not found in row... Code will handle.
@@ -127,7 +129,11 @@
 	 * 
 	 */
 	public static Collection<NotifyBody> getAll() {
-		return bodyMap.values();
+		// Note: The same Notify Body is entered several times with different keys.
+		// Therefore, need a Set of Values, not all the Values.
+		Set<NotifyBody> set = new HashSet<>();
+		set.addAll(bodyMap.values());
+		return set;
 	}
 	
 	/**
@@ -140,14 +146,10 @@
 		ClassLoader cl = Thread.currentThread().getContextClassLoader();
 		Package pkg = NotifyBody.class.getPackage();
 		String path = pkg.getName().replace('.', '/');
-//		Enumeration<URL> urls = cl.getResources(path);
-//		while(urls.hasMoreElements()) {
-//			URL url = urls.nextElement();
 		URL url = cl.getResource(path);
 		if(url == null) {
 			throw new APIException("Cannot load resources from " + path);
 		}
-		System.out.println(url);
 		File dir;
 		try {
 			dir = new File(url.toURI());
@@ -180,6 +182,25 @@
 				}
 			}
 		}
-//		}
 	}
+	
+
+	protected void println(StringBuilder sb, int indent, Object ... objs) {
+		for(int i=0;i<indent;++i) {
+			sb.append(' ');
+		}
+		for(Object o : objs) {
+			sb.append(o.toString());
+		}
+		sb.append('\n');
+	}
+	
+	protected void printCell(StringBuilder sb, int indent, String current, String prev) {
+		if(current.equals(prev)) {
+			println(sb,indent,DUPL);
+		} else {
+			println(sb,indent,"<td>",current,"</td>");
+		}
+	}
+
 }
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 db96d50..2369582 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
@@ -27,10 +27,11 @@
 import org.onap.aaf.auth.env.AuthzTrans;
 import org.onap.aaf.cadi.Access;
 
-public class NotifyCredBody extends AbsCredBody {
+public abstract class NotifyCredBody extends NotifyBody {
+
 	private final String explanation;
 	public NotifyCredBody(Access access, String name) throws IOException {
-		super(name);
+		super("cred",name);
 		
 		// Default
 		explanation = "The following Credentials are expiring on the dates shown. "
@@ -38,17 +39,70 @@
 	}
 
 	@Override
-	public String body(AuthzTrans trans, Notify n, String id) {
-		StringBuilder sb = new StringBuilder();
-		sb.append(explanation);
-		sb.append("<br>");
-		sb.append("<tr>\n" + 
-				"<th>Role</th>\n" + 
-				"<th>Expires</th>\n" + 
-				"</tr>\n");
+	public boolean body(AuthzTrans trans, StringBuilder sb, int indent, Notify n, String id) {
+		println(sb,indent,explanation);
+		println(sb,indent,"<br><br>");
+		println(sb,indent,"<table>");
+		indent+=2;
+		println(sb,indent,"<tr>");
+		indent+=2;
+		println(sb,indent,"<th>Fully Qualified ID</th>");
+		println(sb,indent,"<th>Type</th>");
+		println(sb,indent,"<th>Details</th>");
+		println(sb,indent,"<th>Expires</th>");
+		println(sb,indent,"<th>Cred Detail Page</th>");
+		indent-=2;
+		println(sb,indent,"</tr>");
+		String theid, type, info, gui, expires, notes;
+		String p_theid=null, p_type=null, p_gui=null, p_expires=null;
 		for(List<String> row : rows.get(id)) {
+			theid=row.get(1);
+			switch(row.get(3)) {
+				case "1":
+				case "2":
+					type = "Password";
+				case "200":
+					type = "x509 (Certificate)";
+					break;
+				default:
+					type = "Unknown, see AAF GUI";
+					break;
+			}
+			gui = "<a href=\""+n.guiURL+"/creddetail?ns="+row.get(2)+"\">"+row.get(2)+"</a>";
+			expires = row.get(4);
+			info = row.get(6);
+			notes = row.get(8);
+			if(notes!=null && !notes.isEmpty()) {
+				info += "<br>" + notes; 
+			}
 			
+			println(sb,indent,"<tr>");
+			indent+=2;
+			printCell(sb,indent,theid,p_theid);
+			printCell(sb,indent,type,p_type);
+			printCell(sb,indent,info,null);
+			printCell(sb,indent,expires,p_expires);
+			printCell(sb,indent,gui,p_gui);
+			indent-=2;
+			println(sb,indent,"</tr>");
+			p_theid=theid;
+			p_type=type;
+			p_gui=gui;
+			p_expires=expires;
 		}
-		return sb.toString();
+		indent-=2;
+		println(sb,indent,"</table>");
+		
+		return true;
 	}
+
+	@Override
+	public String user(List<String> row) {
+		if( (row != null) && row.size()>1) {
+			return row.get(1);
+		}
+		return null;
+	}
+
+
 }
diff --git a/auth/auth-batch/src/test/java/org/onap/aaf/auth/batch/helpers/test/JU_Cred.java b/auth/auth-batch/src/test/java/org/onap/aaf/auth/batch/helpers/test/JU_Cred.java
index 20831c6..4861696 100644
--- a/auth/auth-batch/src/test/java/org/onap/aaf/auth/batch/helpers/test/JU_Cred.java
+++ b/auth/auth-batch/src/test/java/org/onap/aaf/auth/batch/helpers/test/JU_Cred.java
@@ -79,7 +79,7 @@
         prop.setProperty(Config.AAF_ROOT_COMPANY,"test");
         define.set(prop);
         
-        instance = new Instance(12, date, integer, 125642678910L);
+        instance = new Instance(12, date, integer, 125642678910L,"234",1,"");
         cred = new Cred("myid1234@aaf.att.com");
     }
     
diff --git a/auth/auth-batch/src/test/java/org/onap/aaf/auth/batch/reports/bodies/JU_AbsCredBodyTest.java b/auth/auth-batch/src/test/java/org/onap/aaf/auth/batch/reports/bodies/JU_AbsCredBodyTest.java
deleted file mode 100644
index e7d226b..0000000
--- a/auth/auth-batch/src/test/java/org/onap/aaf/auth/batch/reports/bodies/JU_AbsCredBodyTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * ============LICENSE_START====================================================
- * org.onap.aaf
- * ===========================================================================
- * Copyright (c) 2019 IBM 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.reports.bodies;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.onap.aaf.auth.batch.reports.Notify;
-import org.onap.aaf.auth.env.AuthzTrans;
-
-public class JU_AbsCredBodyTest {
-
-    @Test
-    public void testUserWithValue() {
-        List<String> row = new ArrayList<>();
-        row.add("test");
-        row.add("user");
-        AbsCredBody absCredBody = new AbsCredBody("") {
-            @Override
-            public String body(AuthzTrans trans, Notify n, String id) {
-                return null;
-            }
-        };
-        Assert.assertEquals("user", absCredBody.user(row));
-    }
-
-    @Test
-    public void testUserWithoutValue() {
-        //String testStr = "test";
-        List<String> row = Collections.emptyList();
-        AbsCredBody absCredBody = new AbsCredBody("") {
-            @Override
-            public String body(AuthzTrans trans, Notify n, String id) {
-                return null;
-            }
-        };
-        Assert.assertNull(absCredBody.user(row));
-    }
-}
\ No newline at end of file
diff --git a/auth/auth-cass/cass_init/init2_10.cql b/auth/auth-cass/cass_init/init2_10.cql
new file mode 100644
index 0000000..8536c03
--- /dev/null
+++ b/auth/auth-cass/cass_init/init2_10.cql
@@ -0,0 +1,3 @@
+use authz;
+alter TABLE cred ADD tag varchar;
+alter TABLE cred ADD attn int;
diff --git a/auth/auth-cass/docker/dbash.sh b/auth/auth-cass/docker/dbash.sh
new file mode 100644
index 0000000..1e13d27
--- /dev/null
+++ b/auth/auth-cass/docker/dbash.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+#########
+#  ============LICENSE_START====================================================
+#  org.onap.aaf
+#  ===========================================================================
+#  Copyright (c) 2017 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====================================================
+#
+# Pull in AAF Env Variables from AAF install
+if [ -e ../../docker/d.props ]; then
+  . ../../docker/d.props
+fi
+DOCKER=${DOCKER:-docker}
+
+$DOCKER exec -it aaf_cass bash
+
diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/org/FileMailer.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/org/FileMailer.java
new file mode 100644
index 0000000..e5fc438
--- /dev/null
+++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/org/FileMailer.java
@@ -0,0 +1,148 @@
+/**
+ * ============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.org;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.onap.aaf.auth.env.AuthzTrans;
+import org.onap.aaf.cadi.Access;
+import org.onap.aaf.misc.env.APIException;
+import org.onap.aaf.misc.env.util.Chrono;
+
+public class FileMailer implements Mailer {
+	private Path dir;
+	private String mail_from;
+	private String testName;
+	private int count;
+
+
+	public FileMailer(Access access) throws APIException {
+		count = 0;
+		
+		mail_from = access.getProperty("MAIL_FROM", null);
+		if(mail_from==null) {
+			throw new APIException("MAIL_FROM property is required for Email Notifications");
+		}
+		String env = access.getProperty("CASS_ENV", "UNKNOWN");
+		String logdir = access.getProperty(env+".LOG_DIR", "logs/"+env);
+		dir = Paths.get(logdir+"/email/"+Chrono.dateOnlyStamp());
+		if(!Files.exists(dir)) {
+			try {
+				Files.createDirectories(dir);
+			} catch (IOException e) {
+				throw new APIException("Cannot create directory: " + dir.toString(),e);
+			}
+		}
+		
+		boolean dryrun = Boolean.parseBoolean(access.getProperty("DRY_RUN","false"));
+		int maxEmail = Integer.parseInt(access.getProperty("MAX_EMAIL", "-1"));
+		if(dryrun && maxEmail==1) {
+			testName = "email_test";
+		} else {
+			testName = null;
+		}
+	}
+
+	@Override
+	public boolean sendEmail(AuthzTrans trans, boolean testMode, List<String> toList, List<String> ccList,
+			String subject, String body, Boolean urgent) throws OrganizationException {
+		boolean status = false;
+		try {
+			Path path;
+			if(testName==null) {
+				path = Files.createTempFile(dir, "email", ".hdr");
+			} else {
+				path = Paths.get(dir.toString(), "emailTEST.hdr");
+			}
+			BufferedWriter bw = Files.newBufferedWriter(path);
+			try {
+				bw.write("TO: ");
+				boolean first = true;
+				for(String to : toList) {
+					if(first) {
+						first = false;
+					} else {
+						bw.write(',');
+					}
+					bw.write(to);
+				}
+				bw.newLine();
+				
+				bw.write("CC: ");
+				first = true;
+				for(String cc : ccList) {
+					if(first) {
+						first = false;
+					} else {
+						bw.write(',');
+					}
+					bw.write(cc);
+				}
+				bw.newLine();
+				
+				bw.write("FROM: ");
+				bw.write(mail_from);
+				bw.newLine();
+				
+				bw.write("SUBJECT: ");
+				bw.write(subject);
+				bw.newLine();
+				
+				if(urgent) {
+					bw.write("Importance: High");  
+					bw.newLine();
+				}
+
+			} finally {
+				bw.close();
+			}
+
+			path = Paths.get(path.toString().replaceAll(".hdr", ".html"));
+			bw = Files.newBufferedWriter(path);
+			try {
+				bw.write(body);
+				bw.newLine();
+			} finally {
+				bw.close();
+			}
+			status = true;
+		} catch ( IOException e) {
+			throw new OrganizationException(e);
+		}
+		++count;
+		return status;
+	}
+
+	@Override
+	public String mailFrom() {
+		return mail_from;
+	}
+
+	@Override
+	public int count() {
+		return count;
+	}
+}
diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/org/Mailer.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/org/Mailer.java
index 1f1c28b..f7c8b48 100644
--- a/auth/auth-core/src/main/java/org/onap/aaf/auth/org/Mailer.java
+++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/org/Mailer.java
@@ -25,10 +25,9 @@
 import org.onap.aaf.auth.env.AuthzTrans;
 
 public interface Mailer {
-    public int sendEmail(
+    public boolean sendEmail(
             AuthzTrans trans,
             boolean testMode,
-            String mailFrom,
             List<String> toList, 
             List<String> ccList, 
             String subject, 
@@ -37,4 +36,6 @@
 
 	public String mailFrom();
 
+	public int count();
+
 }
diff --git a/auth/auth-deforg/src/main/java/org/onap/aaf/org/DefaultOrg.java b/auth/auth-deforg/src/main/java/org/onap/aaf/org/DefaultOrg.java
index c7dd3d3..f1932a2 100644
--- a/auth/auth-deforg/src/main/java/org/onap/aaf/org/DefaultOrg.java
+++ b/auth/auth-deforg/src/main/java/org/onap/aaf/org/DefaultOrg.java
@@ -592,7 +592,7 @@
                 }
             }
 
-            return mailer.sendEmail(trans,dryRun,mailFrom,to,cc,subject,body,urgent);
+            return mailer.sendEmail(trans,dryRun,to,cc,subject,body,urgent)?0:1;
         } else {
             return 0;
         }
diff --git a/auth/auth-service/src/main/java/org/onap/aaf/auth/service/facade/AuthzFacadeImpl.java b/auth/auth-service/src/main/java/org/onap/aaf/auth/service/facade/AuthzFacadeImpl.java
index 1cc8825..e77e090 100644
--- a/auth/auth-service/src/main/java/org/onap/aaf/auth/service/facade/AuthzFacadeImpl.java
+++ b/auth/auth-service/src/main/java/org/onap/aaf/auth/service/facade/AuthzFacadeImpl.java
@@ -282,7 +282,7 @@
                 msgId = "SVC1300";
                 detail = new String[result.variables.length];
                 for(int i=0; i<result.variables.length;++i) {
-                	detail[i]=result.variables.toString();
+                	detail[i]=result.variables[i].toString();
                 }
                 response.setStatus(/*httpstatus=*/300);
                 break;