Update for more Logging Info
[aaf/authz.git] / auth / auth-cass / src / main / java / org / onap / aaf / auth / dao / cass / NsDAO.java
1 /**
2  * ============LICENSE_START====================================================
3  * org.onap.aaf
4  * ===========================================================================
5  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
6  * ===========================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END====================================================
19  *
20  */
21
22 package org.onap.aaf.auth.dao.cass;
23
24 import java.io.ByteArrayOutputStream;
25 import java.io.DataInputStream;
26 import java.io.DataOutputStream;
27 import java.io.IOException;
28 import java.nio.ByteBuffer;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Map.Entry;
35
36 import org.onap.aaf.auth.dao.Bytification;
37 import org.onap.aaf.auth.dao.Cached;
38 import org.onap.aaf.auth.dao.CassAccess;
39 import org.onap.aaf.auth.dao.CassDAOImpl;
40 import org.onap.aaf.auth.dao.Loader;
41 import org.onap.aaf.auth.dao.Streamer;
42 import org.onap.aaf.auth.env.AuthzTrans;
43 import org.onap.aaf.auth.layer.Result;
44 import org.onap.aaf.misc.env.APIException;
45 import org.onap.aaf.misc.env.Env;
46 import org.onap.aaf.misc.env.TimeTaken;
47
48 import java.util.Set;
49
50 import com.datastax.driver.core.Cluster;
51 import com.datastax.driver.core.ResultSet;
52 import com.datastax.driver.core.Row;
53 import com.datastax.driver.core.exceptions.DriverException;
54
55 /**
56  * NsDAO
57  * 
58  * Data Access Object for Namespace Data
59  * 
60  * @author Jonathan
61  *
62  */
63 public class NsDAO extends CassDAOImpl<AuthzTrans,NsDAO.Data> {
64     public static final String TABLE = "ns";
65     public static final String TABLE_ATTRIB = "ns_attrib";
66     public static final int CACHE_SEG = 0x40; // yields segment 0x0-0x3F
67     public static final int USER = 0;
68     public static final int ROOT = 1;
69     public static final int COMPANY=2;
70     public static final int APP = 3;
71
72     private static final String BEGIN_BATCH = "BEGIN BATCH\n";
73     private static final String APPLY_BATCH = "\nAPPLY BATCH;\n";
74     private static final String SQSCCR = "';\n";
75     private static final String SQCSQ = "','";
76     
77     private HistoryDAO historyDAO;
78     private CacheInfoDAO infoDAO;
79     private PSInfo psNS;
80
81     public NsDAO(AuthzTrans trans, Cluster cluster, String keyspace) throws APIException, IOException {
82         super(trans, NsDAO.class.getSimpleName(),cluster,keyspace,Data.class,TABLE, readConsistency(trans,TABLE), writeConsistency(trans,TABLE));
83         init(trans);
84     }
85
86     public NsDAO(AuthzTrans trans, HistoryDAO hDAO, CacheInfoDAO iDAO) throws APIException, IOException {
87         super(trans, NsDAO.class.getSimpleName(),hDAO,Data.class,TABLE, readConsistency(trans,TABLE), writeConsistency(trans,TABLE));
88         historyDAO=hDAO;
89         infoDAO = iDAO;
90         init(trans);
91     }
92
93
94     //////////////////////////////////////////
95     // Data Definition, matches Cassandra DM
96     //////////////////////////////////////////
97     private static final int KEYLIMIT = 1;
98     /**
99      * Data class that matches the Cassandra Table "role"
100      * 
101      * @author Jonathan
102      */
103     public static class Data extends CacheableData implements Bytification {
104         public String              name;
105         public int                  type;
106         public String              description;
107         public String              parent;
108         public Map<String,String> attrib;
109
110 //        ////////////////////////////////////////
111 //        // Getters
112         public Map<String,String> attrib(boolean mutable) {
113             if (attrib == null) {
114                 attrib = new HashMap<>();
115             } else if (mutable && !(attrib instanceof HashMap)) {
116                 attrib = new HashMap<>(attrib);
117             }
118             return attrib;
119         }
120
121         @Override
122         public int[] invalidate(Cached<?,?> cache) {
123             return new int[] {
124                 seg(cache,name)
125             };
126         }
127
128         public NsSplit split(String name) {
129             return new NsSplit(this,name);
130         }
131
132         @Override
133         public ByteBuffer bytify() throws IOException {
134             ByteArrayOutputStream baos = new ByteArrayOutputStream();
135             NSLoader.deflt.marshal(this,new DataOutputStream(baos));
136             return ByteBuffer.wrap(baos.toByteArray());
137         }
138         
139         @Override
140         public void reconstitute(ByteBuffer bb) throws IOException {
141             NSLoader.deflt.unmarshal(this,toDIS(bb));
142         }
143         
144         @Override
145         public String toString() {
146             return name;
147         }
148         
149     }
150     
151     private void init(AuthzTrans trans) throws APIException, IOException {
152         // Set up sub-DAOs
153         if (historyDAO==null) {
154         historyDAO = new HistoryDAO(trans, this);
155     }
156         if (infoDAO==null) {
157         infoDAO = new CacheInfoDAO(trans,this);
158     }
159
160         String[] helpers = setCRUD(trans, TABLE, Data.class, NSLoader.deflt,4/*need to skip attrib */);
161         
162         psNS = new PSInfo(trans, SELECT_SP + helpers[FIELD_COMMAS] + " FROM " + TABLE +
163                 " WHERE parent = ?", new NSLoader(1),readConsistency);
164
165     }
166     
167     private static final class NSLoader extends Loader<Data> implements Streamer<Data> {
168         public static final int MAGIC=250935515;
169         public static final int VERSION=1;
170         public static final int BUFF_SIZE=48;
171
172         public static final NSLoader deflt = new NSLoader(KEYLIMIT);
173         
174         public NSLoader(int keylimit) {
175             super(keylimit);
176         }
177
178         @Override
179         public Data load(Data data, Row row) {
180             // Int more efficient
181             data.name = row.getString(0);
182             data.type = row.getInt(1);
183             data.description = row.getString(2);
184             data.parent = row.getString(3);
185             return data;
186         }
187
188         @Override
189         protected void key(Data data, int idx, Object[] obj) {
190             obj[idx]=data.name;
191         }
192
193         @Override
194         protected void body(Data data, int _idx, Object[] obj) {
195                 int idx = _idx;
196
197             obj[idx]=data.type;
198             obj[++idx]=data.description;
199             obj[++idx]=data.parent;
200         }
201         
202         @Override
203         public void marshal(Data data, DataOutputStream os) throws IOException {
204             writeHeader(os,MAGIC,VERSION);
205             writeString(os, data.name);
206             os.writeInt(data.type);
207             writeString(os,data.description);
208             writeString(os,data.parent);
209             if (data.attrib==null) {
210                 os.writeInt(-1);
211             } else {
212                 os.writeInt(data.attrib.size());
213                 for (Entry<String, String> es : data.attrib(false).entrySet()) {
214                     writeString(os,es.getKey());
215                     writeString(os,es.getValue());
216                 }
217             }
218         }
219
220         @Override
221         public void unmarshal(Data data, DataInputStream is) throws IOException {
222             /*int version = */readHeader(is,MAGIC,VERSION);
223             // If Version Changes between Production runs, you'll need to do a switch Statement, and adequately read in fields
224             
225             byte[] buff = new byte[BUFF_SIZE];
226             data.name = readString(is, buff);
227             data.type = is.readInt();
228             data.description = readString(is,buff);
229             data.parent = readString(is,buff);
230             int count = is.readInt();
231             if (count>0) {
232                 Map<String, String> da = data.attrib(true);
233                 for (int i=0;i<count;++i) {
234                     da.put(readString(is,buff), readString(is,buff));
235                 }
236             }
237         }
238
239     }
240     
241     @Override
242     public Result<Data> create(AuthzTrans trans, Data data) {
243         String ns = data.name;
244         // Ensure Parent is set
245         if (data.parent==null) {
246             return Result.err(Result.ERR_BadData, "Need parent for %s", ns);
247         }
248
249         // insert Attributes
250         StringBuilder stmt = new StringBuilder();
251         stmt.append(BEGIN_BATCH);
252         attribInsertStmts(stmt, data);
253         stmt.append(APPLY_BATCH);
254         try {
255             getSession(trans).execute(stmt.toString());
256 //// TEST CODE for Exception                
257 //            boolean force = true; 
258 //            if (force) {
259 //                throw new com.datastax.driver.core.exceptions.NoHostAvailableException(new HashMap<>());
260 ////                throw new com.datastax.driver.core.exceptions.AuthenticationException(new InetSocketAddress(9999),"Sample Message");
261 //            }
262 ////END TEST CODE
263
264         } catch (DriverException | APIException | IOException e) {
265             reportPerhapsReset(trans,e);
266             trans.info().log(stmt);
267             return Result.err(Result.ERR_Backend, "Backend Access");
268         }
269         return super.create(trans, data);
270     }
271
272     @Override
273     public Result<Void> update(AuthzTrans trans, Data data) {
274         String ns = data.name;
275         // Ensure Parent is set
276         if (data.parent==null) {
277             return Result.err(Result.ERR_BadData, "Need parent for %s", ns);
278         }
279
280         StringBuilder stmt = new StringBuilder();
281         stmt.append(BEGIN_BATCH);
282         try {
283             Map<String, String> localAttr = data.attrib;
284             Result<Map<String, String>> rremoteAttr = readAttribByNS(trans,ns);
285             if (rremoteAttr.notOK()) {
286                 return Result.err(rremoteAttr);
287             }
288             // update Attributes
289             String str;
290             for (Entry<String, String> es : localAttr.entrySet()) {
291                 str = rremoteAttr.value.get(es.getKey());
292                 if (str==null || !str.equals(es.getValue())) {
293                     attribUpdateStmt(stmt, ns, es.getKey(),es.getValue());
294                 }
295             }
296             
297             // No point in deleting... insert overwrites...
298 //            for (Entry<String, String> es : remoteAttr.entrySet()) {
299 //                str = localAttr.get(es.getKey());
300 //                if (str==null || !str.equals(es.getValue())) {
301 //                    attribDeleteStmt(stmt, ns, es.getKey());
302 //                }
303 //            }
304             if (stmt.length()>BEGIN_BATCH.length()) {
305                 stmt.append(APPLY_BATCH);
306                 getSession(trans).execute(stmt.toString());
307             }
308         } catch (DriverException | APIException | IOException e) {
309             reportPerhapsReset(trans,e);
310             trans.info().log(stmt);
311             return Result.err(Result.ERR_Backend, CassAccess.ERR_ACCESS_MSG);
312         }
313
314         return super.update(trans,data);
315     }
316
317     /* (non-Javadoc)
318      * @see org.onap.aaf.auth.dao.CassDAOImpl#read(com.att.inno.env.TransStore, java.lang.Object)
319      */
320     @Override
321     public Result<List<Data>> read(AuthzTrans trans, Data data) {
322         Result<List<Data>> rld = super.read(trans, data);
323         
324         if (rld.isOKhasData()) {
325             for (Data d : rld.value) {
326                 // Note: Map is null at this point, save time/mem by assignment
327                 Result<Map<String, String>> rabn = readAttribByNS(trans,d.name);
328                 if (rabn.isOK()) {
329                     d.attrib = rabn.value;
330                 } else {
331                     return Result.err(rabn);
332                 }
333             }
334         }
335         return rld;
336     }
337
338     /* (non-Javadoc)
339      * @see org.onap.aaf.auth.dao.CassDAOImpl#read(com.att.inno.env.TransStore, java.lang.Object[])
340      */
341     @Override
342     public Result<List<Data>> read(AuthzTrans trans, Object... key) {
343         Result<List<Data>> rld = super.read(trans, key);
344
345         if (rld.isOKhasData()) {
346             for (Data d : rld.value) {
347                 // Note: Map is null at this point, save time/mem by assignment
348                 Result<Map<String, String>> rabn = readAttribByNS(trans,d.name);
349                 if (rabn.isOK()) {
350                     d.attrib = rabn.value;
351                 } else {
352                     return Result.err(rabn);
353                 }
354             }
355         }
356         return rld;
357     }
358
359     @Override
360     public Result<Void> delete(AuthzTrans trans, Data data, boolean reread) {
361         TimeTaken tt = trans.start("Delete NS Attributes " + data.name, Env.REMOTE);
362         try {
363             StringBuilder stmt = new StringBuilder();
364             attribDeleteAllStmt(stmt, data);
365             try {
366                 getSession(trans).execute(stmt.toString());
367             } catch (DriverException | APIException | IOException e) {
368                 reportPerhapsReset(trans,e);
369                 trans.info().log(stmt);
370                 return Result.err(Result.ERR_Backend, CassAccess.ERR_ACCESS_MSG);
371             }
372         } finally {
373             tt.done();
374         }
375         return super.delete(trans, data, reread);
376
377     }
378     
379     public Result<Map<String,String>> readAttribByNS(AuthzTrans trans, String ns) {
380         Map<String,String> map = new HashMap<>();
381         TimeTaken tt = trans.start("readAttribByNS " + ns, Env.REMOTE);
382         try {
383             ResultSet rs = getSession(trans).execute("SELECT key,value FROM " 
384                     + TABLE_ATTRIB 
385                     + " WHERE ns='"
386                     + ns
387                     + "';");
388             
389             for (Iterator<Row> iter = rs.iterator();iter.hasNext(); ) {
390                 Row r = iter.next();
391                 map.put(r.getString(0), r.getString(1));
392             }
393         } catch (DriverException | APIException | IOException e) {
394             reportPerhapsReset(trans,e);
395             return Result.err(Result.ERR_Backend, CassAccess.ERR_ACCESS_MSG);
396         } finally {
397             tt.done();
398         }
399         return Result.ok(map);
400     }
401
402     public Result<Set<String>> readNsByAttrib(AuthzTrans trans, String key) {
403         Set<String> set = new HashSet<>();
404         TimeTaken tt = trans.start("readNsBykey " + key, Env.REMOTE);
405         try {
406             ResultSet rs = getSession(trans).execute("SELECT ns FROM " 
407                 + TABLE_ATTRIB 
408                 + " WHERE key='"
409                 + key
410                 + "';");
411         
412             for (Iterator<Row> iter = rs.iterator();iter.hasNext(); ) {
413                 Row r = iter.next();
414                 set.add(r.getString(0));
415             }
416         } catch (DriverException | APIException | IOException e) {
417             reportPerhapsReset(trans,e);
418             return Result.err(Result.ERR_Backend, CassAccess.ERR_ACCESS_MSG);
419         } finally {
420             tt.done();
421         }
422         return Result.ok(set);
423     }
424
425     public Result<Void> attribAdd(AuthzTrans trans, String ns, String key, String value) {
426         try {
427             getSession(trans).execute(attribInsertStmt(new StringBuilder(),ns,key,value).toString());
428             return Result.ok();
429         } catch (DriverException | APIException | IOException e) {
430             reportPerhapsReset(trans,e);
431             return Result.err(Result.ERR_Backend, CassAccess.ERR_ACCESS_MSG);
432         }
433     }
434     
435     private StringBuilder attribInsertStmt(StringBuilder sb, String ns, String key, String value) {
436         sb.append("INSERT INTO ");
437         sb.append(TABLE_ATTRIB);
438         sb.append(" (ns,key,value) VALUES ('");
439         sb.append(ns);
440         sb.append(SQCSQ);
441         sb.append(key);
442         sb.append(SQCSQ);
443         sb.append(value);
444         sb.append("');");
445         return sb;
446     }
447
448     private StringBuilder attribUpdateStmt(StringBuilder sb, String ns, String key, String value) {
449         sb.append("UPDATE ");
450         sb.append(TABLE_ATTRIB);
451         sb.append(" set value='");
452         sb.append(value);
453         sb.append("' where ns='");
454         sb.append(ns);
455         sb.append("' AND key='");
456         sb.append(key);
457         sb.append("';");
458         return sb;
459     }
460     
461
462     public Result<Void> attribRemove(AuthzTrans trans, String ns, String key) {
463         try {
464             getSession(trans).execute(attribDeleteStmt(new StringBuilder(),ns,key).toString());
465             return Result.ok();
466         } catch (DriverException | APIException | IOException e) {
467             reportPerhapsReset(trans,e);
468             return Result.err(Result.ERR_Backend, CassAccess.ERR_ACCESS_MSG);
469         }
470     }
471     
472     private StringBuilder attribDeleteStmt(StringBuilder stmt, String ns, String key) {
473         stmt.append("DELETE FROM ");
474         stmt.append(TABLE_ATTRIB);
475         stmt.append(" WHERE ns='");
476         stmt.append(ns);
477         stmt.append("' AND key='");
478         stmt.append(key);
479         stmt.append("';");
480         return stmt;
481     }
482     
483     private void attribDeleteAllStmt(StringBuilder stmt, Data data) {
484         stmt.append("  DELETE FROM ");
485         stmt.append(TABLE_ATTRIB);
486         stmt.append(" WHERE ns='");
487         stmt.append(data.name);
488         stmt.append(SQSCCR);
489     }
490
491     private void attribInsertStmts(StringBuilder stmt, Data data) {
492         // INSERT new Attrib
493         for (Entry<String,String> es : data.attrib(false).entrySet() ) {
494             stmt.append("  ");
495             attribInsertStmt(stmt,data.name,es.getKey(),es.getValue());
496         }
497     }
498
499     /**
500      * Add description to Namespace
501      * @param trans
502      * @param ns
503      * @param description
504      * @return
505      */
506     public Result<Void> addDescription(AuthzTrans trans, String ns, String description) {
507         try {
508             getSession(trans).execute(UPDATE_SP + TABLE + " SET description = '" 
509                 + description.replace("'", "''") + "' WHERE name = '" + ns + "';");
510         } catch (DriverException | APIException | IOException e) {
511             reportPerhapsReset(trans,e);
512             return Result.err(Result.ERR_Backend, CassAccess.ERR_ACCESS_MSG);
513         }
514
515         Data data = new Data();
516         data.name=ns;
517         wasModified(trans, CRUD.update, data, "Added description " + description + " to namespace " + ns, null );
518         return Result.ok();
519     }
520
521     public Result<List<Data>> getChildren(AuthzTrans trans, String parent) {
522         return psNS.read(trans, R_TEXT, new Object[]{parent});
523     }
524         
525
526     /**
527      * Log Modification statements to History
528      * 
529      * @param modified           which CRUD action was done
530      * @param data               entity data that needs a log entry
531      * @param overrideMessage    if this is specified, we use it rather than crafting a history message based on data
532      */
533     @Override
534     protected void wasModified(AuthzTrans trans, CRUD modified, Data data, String ... override) {
535         boolean memo = override.length>0 && override[0]!=null;
536         boolean subject = override.length>1 && override[1]!=null;
537
538         //TODO Must log history
539         HistoryDAO.Data hd = HistoryDAO.newInitedData();
540         hd.user = trans.user();
541         hd.action = modified.name();
542         hd.target = TABLE;
543         hd.subject = subject ? override[1] : data.name;
544         hd.memo = memo ? override[0] : (data.name + " was "  + modified.name() + 'd' );
545         if (modified==CRUD.delete) {
546             try {
547                 hd.reconstruct = data.bytify();
548             } catch (IOException e) {
549                 trans.error().log(e,"Could not serialize NsDAO.Data");
550             }
551         }
552
553         if (historyDAO.create(trans, hd).status!=Status.OK) {
554         trans.error().log("Cannot log to History");
555     }
556         if (infoDAO.touch(trans, TABLE,data.invalidate(cache)).notOK()) {
557         trans.error().log("Cannot touch CacheInfo");
558     }
559     }
560
561 }