changed to unmaintained
[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 public abstract class Cmd {
58     // Sonar claims DateFormat is not thread safe.  Leave as Instance Variable.
59     private 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 static final 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 static final ConcurrentHashMap<Class<?>,RosettaDF<?>> dfs = new ConcurrentHashMap<>();
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<>();
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) {
159             sb.append(' ');
160         }
161         for (int i=indent;i<length;++i) {
162             sb.append(c);
163         }
164     }
165
166     public StringBuilder build(StringBuilder sb, StringBuilder detail) {
167         if (name!=null) {
168             sb.append(name);
169             sb.append(' ');
170         }
171         int line = sb.lastIndexOf("\n")+1;
172         if (line<0) {
173             line=0;
174         }
175         int indent = sb.length()-line;
176         for (Param p : params) {
177             sb.append(p.required?'<':'[');
178             sb.append(p.tag);
179             sb.append(p.required?"> ": "] ");
180         }
181
182         boolean first = true;
183         for (Cmd child : children) {
184             if (!(child instanceof DeprecatedCMD)) {
185                 if (first) {
186                     first = false;
187                 } else if (detail==null) {
188                     multiChar(sb,indent,' ',0);
189                 } else {
190                     // Write parents for Detailed Report
191                     Stack<String> stack = new Stack<>();
192                     for (Cmd c = child.parent;c!=null;c=c.parent) {
193                         if (c.name!=null) {
194                             stack.push(c.name);
195                         }
196                     }
197                     if (!stack.isEmpty()) {
198                         sb.append("  ");
199                         while (!stack.isEmpty()) {
200                             sb.append(stack.pop());
201                             sb.append(' ');
202                         }
203                     }
204                 }
205                 child.build(sb,detail);
206                 if (detail!=null) {
207                     child.detailedHelp(4, detail);
208                     // If Child wrote something, then add, bracketing by lines
209                     if (detail.length()>0) {
210                         multiChar(sb,80,'-',2);
211                         sb.append(detail);
212                         sb.append('\n');
213                         multiChar(sb,80,'-',2);
214                         sb.append('\n');
215                         detail.setLength(0); // reuse
216                     } else {
217                         sb.append('\n');
218                     }
219                 }
220             }
221         }
222         return sb;
223     }
224
225     protected void error(Future<?> future) {
226         StringBuilder sb = new StringBuilder("Failed");
227         String desc = future.body();
228         int code = future.code();
229         if (desc==null || desc.length()==0) {
230             withCode(sb,code);
231         } else if (desc.startsWith("{")) {
232             StringReader sr = new StringReader(desc);
233             try {
234                 // Note: 11-18-2013, JonathanGathman.  This rather convoluted Message Structure required by TSS Restful Specs, reflecting "Northbound" practices.
235                 Error err = getDF(Error.class).newData().in(TYPE.JSON).load(sr).asObject();
236                 sb.append(" [");
237                 sb.append(err.getMessageId());
238                 sb.append("]: ");
239                 String messageBody = err.getText();
240                 List<String> vars = err.getVariables();
241                 int pipe;
242                 for (int varCounter=0;varCounter<vars.size();) {
243                     String var = vars.get(varCounter);
244                     ++varCounter;
245                     if (messageBody.indexOf("%" + varCounter) >= 0) {
246                         if ((pipe = var.indexOf('|'))>=0) {  // In AAF, we use a PIPE for Choice
247                             if (aafcli.isTest()) {
248                                 String expiresStr = var.substring(pipe);
249                                 var = var.replace(expiresStr, "[Placeholder]");
250                             } else {
251                                 StringBuilder varsb = new StringBuilder(var);
252                                 varsb.deleteCharAt(pipe);
253                                 var = varsb.toString();
254                             }
255                             messageBody = messageBody.replace("%" + varCounter, varCounter-1 + ") " + var);
256                         } else {
257                             messageBody = messageBody.replace("%" + varCounter, var);
258                         }
259                     }
260                 }
261                 sb.append(messageBody);
262             } catch (Exception e) {
263                 withCode(sb,code);
264                 sb.append(" (Note: Details cannot be obtained from Error Structure)");
265             }
266         } else if (desc.startsWith("<html>")){ // Core Jetty, etc sends HTML for Browsers
267             withCode(sb,code);
268         } else {
269             sb.append(" with code ");
270             sb.append(code);
271             sb.append(", ");
272             sb.append(desc);
273         }
274         pw().println(sb.toString());
275     }
276
277
278     private void withCode(StringBuilder sb, Integer code) {
279         sb.append(" with code ");
280         sb.append(code);
281         switch(code) {
282             case 401:
283                 sb.append(" (HTTP Not Authenticated)");
284                 break;
285             case 403:
286                 sb.append(" (HTTP Forbidden)");
287                 break;
288             case 404:
289                 sb.append(" (HTTP Not Found)");
290                 break;
291             default:
292         }
293     }
294
295     /**
296      * Consistently set start and end dates from Requests (all derived from Request)
297      * @param req
298      */
299     protected void setStartEnd(Request req) {
300         // Set Start/End Dates, if exist
301         String str;
302         if ((str = access.getProperty(Cmd.STARTDATE,null))!=null) {
303             req.setStart(Chrono.timeStamp(Date.valueOf(str)));
304         }
305
306         if ((str = access.getProperty(Cmd.ENDDATE,null))!=null) {
307             req.setEnd(Chrono.timeStamp(Date.valueOf(str)));
308         }
309     }
310
311     /**
312      * For Derived classes, who have ENV in this parent
313      *
314      * @param cls
315      * @return
316      * @throws APIException
317      */
318     protected <T> RosettaDF<T> getDF(Class<T> cls) throws APIException {
319         return getDF(env,cls);
320     }
321
322     /**
323      * This works well, making available for GUI, etc.
324      * @param env
325      * @param cls
326      * @return
327      * @throws APIException
328      */
329     @SuppressWarnings("unchecked")
330     public static <T> RosettaDF<T> getDF(AuthzEnv env, Class<T> cls) throws APIException {
331         RosettaDF<T> rdf = (RosettaDF<T>)dfs.get(cls);
332         if (rdf == null) {
333             rdf = env.newDataFactory(cls);
334             dfs.put(cls, rdf);
335         }
336         return rdf;
337     }
338
339     public void activity(History history, String header) {
340         if (history.getItem().isEmpty()) {
341             int start = header.indexOf('[');
342             if (start >= 0) {
343                 pw().println("No Activity Found for " + header.substring(start));
344             }
345         } else {
346             pw().println(header);
347             for (int i=0;i<lineLength;++i) {
348                 pw().print('-');
349             }
350             pw().println();
351
352             pw().format(hformat,"Date","Table","User","Memo");
353             for (int i=0;i<lineLength;++i) {
354                 pw().print('-');
355             }
356             pw().println();
357
358             // Save Server time by Sorting locally
359             List<Item> items = history.getItem();
360             java.util.Collections.sort(items, (Comparator<Item>) (o1, o2) -> o2.getTimestamp().compare(o1.getTimestamp()));
361
362             for (History.Item item : items) {
363                 GregorianCalendar gc = item.getTimestamp().toGregorianCalendar();
364                 pw().format(hformat,
365                     dateFmt.format(gc.getTime()),
366                     item.getTarget(),
367                     item.getUser(),
368                     item.getMemo());
369             }
370         }
371     }
372
373     /**
374      * Turn String Array into a | delimited String
375      * @param options
376      * @return
377      */
378     public static String optionsToString(String[] options) {
379         StringBuilder sb = new StringBuilder();
380         boolean first = true;
381         for (String s : options) {
382             if (first) {
383                 first = false;
384             } else {
385                 sb.append('|');
386             }
387             sb.append(s);
388         }
389         return sb.toString();
390     }
391
392     /**
393      * return which index number the Option matches.
394      *
395      * throws an Exception if not part of this Option Set
396      *
397      * @param options
398      * @param test
399      * @return
400      * @throws Exception
401      */
402     public int whichOption(String[] options, String test) throws CadiException {
403         for (int i=0;i<options.length;++i) {
404             if (options[i].equals(test)) {
405                 return i;
406             }
407         }
408         pw().printf("%s is not a valid cmd\n",test);
409         throw new CadiException(build(new StringBuilder("Invalid Option: "),null).toString());
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) {
478             pw().print('-');
479         }
480         pw().println();
481     }
482
483     protected void setQueryParamsOn(Rcli<?> rcli) {
484         StringBuilder sb=null;
485         String force;
486         if ((force=aafcli.forceString())!=null) {
487             sb = new StringBuilder("force=");
488             sb.append(force);
489         }
490         if (aafcli.addRequest()) {
491             if (sb==null) {
492                 sb = new StringBuilder("future=true");
493             } else {
494                 sb.append("&future=true");
495             }
496         }
497         if (sb!=null && rcli!=null) {
498             rcli.setQueryParams(sb.toString());
499         }
500     }
501 //
502 //    /**
503 //     * If Force is set, will return True once only, then revert to "FALSE".
504 //     *
505 //     * @return
506 //     */
507 //    protected String checkForce() {
508 //        if (TRUE.equalsIgnoreCase(env.getProperty(FORCE, FALSE))) {
509 //            env.setProperty(FORCE, FALSE);
510 //            return "true";
511 //        }
512 //        return FALSE;
513 //    }
514
515     public String toString() {
516         StringBuilder sb = new StringBuilder();
517         if (parent==null) { // ultimate parent
518             build(sb,null);
519             return sb.toString();
520         } else {
521             return parent.toString();
522         }
523     }
524
525     /**
526      * Appends shortID with Realm, but only when allowed by Organization
527      * @throws OrganizationException
528      */
529     public String fullID(String id) {
530         if (id != null) {
531             if (id.indexOf('@') < 0) {
532                 id+=defaultRealm;
533             } else {
534                 return id; // is already a full ID
535             }
536         }
537         return id;
538     }
539 }