Mass removal of all Tabs (Style Warnings)
[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<>();
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<>();
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, JonathanGathman.  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 }