896cbb3087f762b3035df072543f3fa7e9ae21b3
[aaf/authz.git] / auth / auth-cmd / src / main / java / org / onap / aaf / auth / cmd / Cmd.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.cmd;
23
24 import java.io.PrintWriter;
25 import java.io.StringReader;
26 import java.sql.Date;
27 import java.text.DateFormat;
28 import java.text.SimpleDateFormat;
29 import java.util.ArrayList;
30 import java.util.Comparator;
31 import java.util.GregorianCalendar;
32 import java.util.List;
33 import java.util.Stack;
34 import java.util.concurrent.ConcurrentHashMap;
35
36 import org.onap.aaf.auth.env.AuthzEnv;
37 import org.onap.aaf.auth.org.OrganizationException;
38 import org.onap.aaf.auth.rserv.HttpMethods;
39 import org.onap.aaf.cadi.Access;
40 import org.onap.aaf.cadi.CadiException;
41 import org.onap.aaf.cadi.LocatorException;
42 import org.onap.aaf.cadi.client.Future;
43 import org.onap.aaf.cadi.client.Rcli;
44 import org.onap.aaf.cadi.client.Retryable;
45 import org.onap.aaf.cadi.config.Config;
46 import org.onap.aaf.cadi.http.HMangr;
47 import org.onap.aaf.misc.env.APIException;
48 import org.onap.aaf.misc.env.Data.TYPE;
49 import org.onap.aaf.misc.env.util.Chrono;
50 import org.onap.aaf.misc.rosetta.env.RosettaDF;
51
52 import aaf.v2_0.Error;
53 import aaf.v2_0.History;
54 import aaf.v2_0.History.Item;
55 import aaf.v2_0.Request;
56
57
58 public abstract class Cmd {
59         // Sonar claims DateFormat is not thread safe.  Leave as Instance Variable.
60         private final DateFormat dateFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
61         protected static final String BLANK = "";
62         protected static final String COMMA = ","; // for use in splits
63
64         protected static final int lineLength = 80;
65
66         private final static String hformat = "%-23s %-5s %-20s %-35s\n";
67
68         public static final String STARTDATE = "startdate";
69         public static final String ENDDATE = "enddate";
70         
71         private String name;
72         private final Param[] params;
73         private int required;
74         protected final Cmd parent;
75         protected final List<Cmd> children;
76         private final static ConcurrentHashMap<Class<?>,RosettaDF<?>> dfs = new ConcurrentHashMap<Class<?>,RosettaDF<?>>();
77         public final AAFcli aafcli;
78         protected Access access;
79         private AuthzEnv env;
80         private final String defaultRealm;
81
82         public Cmd(AAFcli aafcli, String name, Param ... params) {
83                 this(aafcli,null, name,params);
84         }
85
86         public Cmd(Cmd parent, String name, Param ... params) {
87                 this(parent.aafcli,parent, name,params);
88         }
89
90         Cmd(AAFcli aafcli, Cmd parent, String name, Param ... params) {
91                 this.parent = parent;
92                 this.aafcli = aafcli;
93                 this.env = aafcli.env;
94                 this.access = aafcli.access;
95                 if(parent!=null) {
96                         parent.children.add(this);
97                 }
98                 children = new ArrayList<Cmd>();
99                 this.params = params;
100                 this.name = name;
101                 required=0;
102                 for(Param p : params) {
103                         if(p.required) {
104                                 ++required;
105                         }
106                 }
107                 
108                 String temp = access.getProperty(Config.AAF_DEFAULT_REALM,null);
109                 if(temp!=null && !temp.startsWith("@")) {
110                         defaultRealm = '@' + temp;
111                 } else {
112                         defaultRealm="<Set Default Realm>";
113                 }
114         }
115         
116         public final int exec(int idx, String ... args) throws CadiException, APIException, LocatorException {
117                 if(args.length-idx<required) {
118                         throw new CadiException(build(new StringBuilder("Too few args: "),null).toString());
119                 }
120                 return _exec(idx,args);
121         }
122         
123         protected abstract int _exec(int idx, final String ... args) throws CadiException, APIException, LocatorException;
124         
125         public void detailedHelp(int indent,StringBuilder sb) {
126         }
127
128         protected void detailLine(StringBuilder sb, int length, String s) {
129                 multiChar(sb,length,' ',0);
130                 sb.append(s);
131         }
132
133         public void apis(int indent,StringBuilder sb) {
134         }
135
136         protected void api(StringBuilder sb, int indent, HttpMethods meth, String pathInfo, Class<?> cls,boolean head) {
137             final String smeth = meth.name();
138                 if(head) {
139                         sb.append('\n');
140                         detailLine(sb,indent,"APIs:");
141                 }
142                 indent+=2;
143                 multiChar(sb,indent,' ',0);
144                 sb.append(smeth);
145                 sb.append(' ');
146                 sb.append(pathInfo);
147                 String cliString = aafcli.typeString(cls,true);
148                 if(indent+smeth.length()+pathInfo.length()+cliString.length()+2>80) {
149                         sb.append(" ...");
150                         multiChar(sb,indent+3+smeth.length(),' ',0);
151                 } else { // same line
152                         sb.append(' ');
153                 }
154                 sb.append(cliString);
155         }
156
157         protected void multiChar(StringBuilder sb, int length, char c, int indent) {
158                 sb.append('\n');
159                 for(int i=0;i<indent;++i)sb.append(' ');
160                 for(int i=indent;i<length;++i)sb.append(c);
161         }
162
163         public StringBuilder build(StringBuilder sb, StringBuilder detail) {
164                 if(name!=null) {
165                         sb.append(name);
166                         sb.append(' ');
167                 }
168                 int line = sb.lastIndexOf("\n")+1;
169                 if(line<0) {
170                         line=0;
171                 }
172                 int indent = sb.length()-line;
173                 for(Param p : params) {
174                         sb.append(p.required?'<':'[');
175                         sb.append(p.tag);
176                         sb.append(p.required?"> ": "] ");
177                 }
178                 
179                 boolean first = true;
180                 for(Cmd child : children) {
181                         if(!(child instanceof DeprecatedCMD)) {
182                                 if(first) {
183                                         first = false;
184                                 } else if(detail==null) {
185                                         multiChar(sb,indent,' ',0);
186                                 } else {
187                                         // Write parents for Detailed Report
188                                         Stack<String> stack = new Stack<String>();
189                                         for(Cmd c = child.parent;c!=null;c=c.parent) {
190                                                 if(c.name!=null) {
191                                                         stack.push(c.name);
192                                                 }
193                                         }
194                                         if(!stack.isEmpty()) {
195                                                 sb.append("  ");
196                                                 while(!stack.isEmpty()) {
197                                                         sb.append(stack.pop());
198                                                         sb.append(' ');
199                                                 }
200                                         }
201                                 }
202                                 child.build(sb,detail);
203                                 if(detail!=null) {
204                                         child.detailedHelp(4, detail);
205                                         // If Child wrote something, then add, bracketing by lines
206                                         if(detail.length()>0) {
207                                                 multiChar(sb,80,'-',2);
208                                                 sb.append(detail);
209                                                 sb.append('\n');
210                                                 multiChar(sb,80,'-',2);
211                                                 sb.append('\n');
212                                                 detail.setLength(0); // reuse
213                                         } else {
214                                                 sb.append('\n');
215                                         }
216                                 }
217                         }
218                 }
219                 return sb;
220         }
221         
222         protected void error(Future<?> future) {
223                 StringBuilder sb = new StringBuilder("Failed");
224                 String desc = future.body();
225                 int code = future.code();
226                 if(desc==null || desc.length()==0) {
227                         withCode(sb,code);
228                 } else if(desc.startsWith("{")) {
229                         StringReader sr = new StringReader(desc);
230                         try {
231                                 // Note: 11-18-2013, JG1555.  This rather convoluted Message Structure required by TSS Restful Specs, reflecting "Northbound" practices.
232                                 Error err = getDF(Error.class).newData().in(TYPE.JSON).load(sr).asObject();
233                                 sb.append(" [");
234                                 sb.append(err.getMessageId());
235                                 sb.append("]: ");
236                                 String messageBody = err.getText();
237                                 List<String> vars = err.getVariables();
238                                 int pipe;
239                                 for (int varCounter=0;varCounter<vars.size();) {
240                                         String var = vars.get(varCounter);
241                                         ++varCounter;
242                                         if (messageBody.indexOf("%" + varCounter) >= 0) {
243                                                 if((pipe = var.indexOf('|'))>=0) {  // In AAF, we use a PIPE for Choice
244                                                         if (aafcli.isTest()) {
245                                                                 String expiresStr = var.substring(pipe);
246                                                                 var = var.replace(expiresStr, "[Placeholder]");
247                                                         } else {
248                                                                 StringBuilder varsb = new StringBuilder(var);
249                                                                 varsb.deleteCharAt(pipe);
250                                                                 var = varsb.toString();
251                                                         }
252                                                         messageBody = messageBody.replace("%" + varCounter, varCounter-1 + ") " + var);
253                                                 } else {
254                                                         messageBody = messageBody.replace("%" + varCounter, var);
255                                                 }
256                                         }
257                                 }
258                                 sb.append(messageBody);
259                         } catch (Exception e) {
260                                 withCode(sb,code);
261                                 sb.append(" (Note: Details cannot be obtained from Error Structure)");
262                         }
263                 } else if(desc.startsWith("<html>")){ // Core Jetty, etc sends HTML for Browsers
264                         withCode(sb,code);
265                 } else {
266                         sb.append(" with code ");
267                         sb.append(code);
268                         sb.append(", ");
269                         sb.append(desc);
270                 }
271                 pw().println(sb);
272         }
273
274         
275         private void withCode(StringBuilder sb, Integer code) {
276                 sb.append(" with code ");
277                 sb.append(code);
278                 switch(code) {
279                         case 401:
280                                 sb.append(" (HTTP Not Authenticated)");
281                                 break;
282                         case 403:
283                                 sb.append(" (HTTP Forbidden)");
284                                 break;
285                         case 404:
286                                 sb.append(" (HTTP Not Found)");
287                                 break;
288                         default:
289                 }
290         }
291
292         /**
293          * Consistently set start and end dates from Requests (all derived from Request)
294          * @param req
295          */
296         protected void setStartEnd(Request req) {
297                 // Set Start/End Dates, if exist
298                 String str;
299                 if((str = access.getProperty(Cmd.STARTDATE,null))!=null) {
300                         req.setStart(Chrono.timeStamp(Date.valueOf(str)));
301                 }
302                 
303                 if((str = access.getProperty(Cmd.ENDDATE,null))!=null) {
304                         req.setEnd(Chrono.timeStamp(Date.valueOf(str)));
305                 }
306         }
307
308         /**
309          * For Derived classes, who have ENV in this parent
310          * 
311          * @param cls
312          * @return
313          * @throws APIException
314          */
315         protected <T> RosettaDF<T> getDF(Class<T> cls) throws APIException {
316                 return getDF(env,cls);
317         }
318
319         /**
320          * This works well, making available for GUI, etc.
321          * @param env
322          * @param cls
323          * @return
324          * @throws APIException
325          */
326         @SuppressWarnings("unchecked")
327         public static <T> RosettaDF<T> getDF(AuthzEnv env, Class<T> cls) throws APIException {
328                 RosettaDF<T> rdf = (RosettaDF<T>)dfs.get(cls);
329                 if(rdf == null) {
330                         rdf = env.newDataFactory(cls);
331                         dfs.put(cls, rdf);
332                 }
333                 return rdf;
334         }
335
336         public void activity(History history, String header) {
337                 if (history.getItem().isEmpty()) {
338                         int start = header.indexOf('[');
339                         if (start >= 0) {
340                                 pw().println("No Activity Found for " + header.substring(start));
341                         }
342                 } else {
343                         pw().println(header);
344                         for(int i=0;i<lineLength;++i)pw().print('-');
345                         pw().println();
346                                                                 
347                         pw().format(hformat,"Date","Table","User","Memo");
348                         for(int i=0;i<lineLength;++i)pw().print('-');
349                         pw().println();
350         
351                         // Save Server time by Sorting locally
352                         List<Item> items = history.getItem();
353                         java.util.Collections.sort(items, new Comparator<Item>() {
354                                 @Override
355                                 public int compare(Item o1, Item o2) {
356                                         return o2.getTimestamp().compare(o1.getTimestamp());
357                                 }
358                         });
359                         
360                         for(History.Item item : items) {
361                                 GregorianCalendar gc = item.getTimestamp().toGregorianCalendar();
362                                 pw().format(hformat,
363                                         dateFmt.format(gc.getTime()),
364                                         item.getTarget(),
365                                         item.getUser(),
366                                         item.getMemo());
367                         }
368                 }
369         }
370         
371         /**
372          * Turn String Array into a | delimited String
373          * @param options
374          * @return
375          */
376         public static String optionsToString(String[] options) {
377                 StringBuilder sb = new StringBuilder();
378                 boolean first = true;
379                 for(String s : options) {
380                         if(first) {
381                                 first = false;
382                         } else {
383                                 sb.append('|');
384                         }
385                         sb.append(s);
386                 }
387                 return sb.toString();
388         }
389         
390         /**
391          * return which index number the Option matches.
392          * 
393          * throws an Exception if not part of this Option Set
394          * 
395          * @param options
396          * @param test
397          * @return
398          * @throws Exception
399          */
400         public int whichOption(String[] options, String test) throws CadiException {
401                 for(int i=0;i<options.length;++i) {
402                         if(options[i].equals(test)) {
403                                 return i;
404                         }
405                 }
406                 throw new CadiException(build(new StringBuilder("Invalid Option: "),null).toString());
407         }
408
409 //      protected RosettaEnv env() {
410 //              return aafcli.env;
411 //      }
412
413         protected HMangr hman() {
414                 return aafcli.hman;
415         }
416
417         public<RET> RET same(Retryable<RET> retryable) throws APIException, CadiException, LocatorException {
418                 // We're storing in AAFCli, because we know it's always the same, and single threaded
419                 if(aafcli.prevCall!=null) {
420                         retryable.item(aafcli.prevCall.item());
421                         retryable.lastClient=aafcli.prevCall.lastClient;
422                 }
423                 
424                 RET ret = aafcli.hman.same(aafcli.ss,retryable);
425                 
426                 // Store last call in AAFcli, because Cmds are all different instances.
427                 aafcli.prevCall = retryable;
428                 return ret;
429         }
430
431         public<RET> RET all(Retryable<RET> retryable) throws APIException, CadiException, LocatorException {
432                 this.setQueryParamsOn(retryable.lastClient);
433                 return aafcli.hman.all(aafcli.ss,retryable);
434         }
435
436         public<RET> RET oneOf(Retryable<RET> retryable,String host) throws APIException, CadiException, LocatorException {
437                 this.setQueryParamsOn(retryable.lastClient);
438                 return aafcli.hman.oneOf(aafcli.ss,retryable,true,host);
439         }
440
441         protected PrintWriter pw() {
442                 return AAFcli.pw;
443         }
444
445         public String getName() {
446                 return name;
447         }
448         
449         public void reportHead(String ... str) {
450                 pw().println();
451                 boolean first = true;
452                 int i=0;
453                 for(String s : str) {
454                         if(first) {
455                                 if(++i>1) {
456                                         first = false;
457                                         pw().print("[");
458                                 }
459                         } else {
460                                 pw().print("] [");
461                         }
462                         pw().print(s);
463                 }
464                 if(!first) {
465                         pw().print(']');
466                 }
467                 pw().println();
468                 reportLine();
469         }
470         
471         public String reportColHead(String format, String ...  args) {
472                 pw().format(format,(Object[])args);
473                 reportLine();
474                 return format;
475         }
476
477         public void reportLine() {
478                 for(int i=0;i<lineLength;++i)pw().print('-');
479                 pw().println();
480         }
481         
482         protected void setQueryParamsOn(Rcli<?> rcli) {
483                 StringBuilder sb=null;
484                 String force;
485                 if((force=aafcli.forceString())!=null) {
486                         sb = new StringBuilder("force=");
487                         sb.append(force);
488                 }
489                 if(aafcli.addRequest()) {
490                         if(sb==null) {
491                                 sb = new StringBuilder("future=true");
492                         } else {
493                                 sb.append("&future=true");
494                         }
495                 }
496                 if(sb!=null && rcli!=null) {
497                         rcli.setQueryParams(sb.toString());
498                 }
499         }
500 //
501 //      /**
502 //       * If Force is set, will return True once only, then revert to "FALSE".
503 //       *  
504 //       * @return
505 //       */
506 //      protected String checkForce() {
507 //              if(TRUE.equalsIgnoreCase(env.getProperty(FORCE, FALSE))) {
508 //                      env.setProperty(FORCE, FALSE);
509 //                      return "true";
510 //              }
511 //              return FALSE;
512 //      }
513
514         public String toString() {
515                 StringBuilder sb = new StringBuilder();
516                 if(parent==null) { // ultimate parent
517                         build(sb,null);
518                         return sb.toString();
519                 } else {
520                         return parent.toString();
521                 }
522         }
523         
524 //      private String getOrgRealm() {
525 //              return ;
526 //      }
527 //      
528         /**
529          * Appends shortID with Realm, but only when allowed by Organization
530          * @throws OrganizationException 
531          */
532         public String fullID(String id) {
533                 if(id != null) {
534                         if (id.indexOf('@') < 0) {
535                                 id+=defaultRealm;
536                         } else {
537                                 return id; // is already a full ID
538                         }
539                 }
540                 return id;
541         }
542 }