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