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