AT&T 2.0.19 Code drop, stage 3
[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<String,String>();
114                         } else if (mutable && !(attrib instanceof HashMap)) {
115                                 attrib = new HashMap<String,String>(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<InetSocketAddress,Throwable>());
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<String,String>();
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<String>();
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 }