2 * ============LICENSE_START====================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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====================================================
22 package org.onap.aaf.auth.cmd;
24 import java.io.PrintWriter;
25 import java.io.StringReader;
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;
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;
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;
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
64 protected static final int lineLength = 80;
66 private final static String hformat = "%-23s %-5s %-20s %-35s\n";
68 public static final String STARTDATE = "startdate";
69 public static final String ENDDATE = "enddate";
72 private final Param[] params;
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;
80 private final String defaultRealm;
82 public Cmd(AAFcli aafcli, String name, Param ... params) {
83 this(aafcli,null, name,params);
86 public Cmd(Cmd parent, String name, Param ... params) {
87 this(parent.aafcli,parent, name,params);
90 Cmd(AAFcli aafcli, Cmd parent, String name, Param ... params) {
93 this.env = aafcli.env;
94 this.access = aafcli.access;
96 parent.children.add(this);
98 children = new ArrayList<>();
102 for (Param p : params) {
108 String temp = access.getProperty(Config.AAF_DEFAULT_REALM,null);
109 if (temp!=null && !temp.startsWith("@")) {
110 defaultRealm = '@' + temp;
112 defaultRealm="<Set Default Realm>";
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());
120 return _exec(idx,args);
123 protected abstract int _exec(int idx, final String ... args) throws CadiException, APIException, LocatorException;
125 public void detailedHelp(int indent,StringBuilder sb) {
128 protected void detailLine(StringBuilder sb, int length, String s) {
129 multiChar(sb,length,' ',0);
133 public void apis(int indent,StringBuilder sb) {
136 protected void api(StringBuilder sb, int indent, HttpMethods meth, String pathInfo, Class<?> cls,boolean head) {
137 final String smeth = meth.name();
140 detailLine(sb,indent,"APIs:");
143 multiChar(sb,indent,' ',0);
147 String cliString = aafcli.typeString(cls,true);
148 if (indent+smeth.length()+pathInfo.length()+cliString.length()+2>80) {
150 multiChar(sb,indent+3+smeth.length(),' ',0);
151 } else { // same line
154 sb.append(cliString);
157 protected void multiChar(StringBuilder sb, int length, char c, int indent) {
159 for (int i=0;i<indent;++i)sb.append(' ');
160 for (int i=indent;i<length;++i)sb.append(c);
163 public StringBuilder build(StringBuilder sb, StringBuilder detail) {
168 int line = sb.lastIndexOf("\n")+1;
172 int indent = sb.length()-line;
173 for (Param p : params) {
174 sb.append(p.required?'<':'[');
176 sb.append(p.required?"> ": "] ");
179 boolean first = true;
180 for (Cmd child : children) {
181 if (!(child instanceof DeprecatedCMD)) {
184 } else if (detail==null) {
185 multiChar(sb,indent,' ',0);
187 // Write parents for Detailed Report
188 Stack<String> stack = new Stack<String>();
189 for (Cmd c = child.parent;c!=null;c=c.parent) {
194 if (!stack.isEmpty()) {
196 while (!stack.isEmpty()) {
197 sb.append(stack.pop());
202 child.build(sb,detail);
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);
210 multiChar(sb,80,'-',2);
212 detail.setLength(0); // reuse
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) {
228 } else if (desc.startsWith("{")) {
229 StringReader sr = new StringReader(desc);
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();
234 sb.append(err.getMessageId());
236 String messageBody = err.getText();
237 List<String> vars = err.getVariables();
239 for (int varCounter=0;varCounter<vars.size();) {
240 String var = vars.get(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]");
248 StringBuilder varsb = new StringBuilder(var);
249 varsb.deleteCharAt(pipe);
250 var = varsb.toString();
252 messageBody = messageBody.replace("%" + varCounter, varCounter-1 + ") " + var);
254 messageBody = messageBody.replace("%" + varCounter, var);
258 sb.append(messageBody);
259 } catch (Exception e) {
261 sb.append(" (Note: Details cannot be obtained from Error Structure)");
263 } else if (desc.startsWith("<html>")){ // Core Jetty, etc sends HTML for Browsers
266 sb.append(" with code ");
275 private void withCode(StringBuilder sb, Integer code) {
276 sb.append(" with code ");
280 sb.append(" (HTTP Not Authenticated)");
283 sb.append(" (HTTP Forbidden)");
286 sb.append(" (HTTP Not Found)");
293 * Consistently set start and end dates from Requests (all derived from Request)
296 protected void setStartEnd(Request req) {
297 // Set Start/End Dates, if exist
299 if ((str = access.getProperty(Cmd.STARTDATE,null))!=null) {
300 req.setStart(Chrono.timeStamp(Date.valueOf(str)));
303 if ((str = access.getProperty(Cmd.ENDDATE,null))!=null) {
304 req.setEnd(Chrono.timeStamp(Date.valueOf(str)));
309 * For Derived classes, who have ENV in this parent
313 * @throws APIException
315 protected <T> RosettaDF<T> getDF(Class<T> cls) throws APIException {
316 return getDF(env,cls);
320 * This works well, making available for GUI, etc.
324 * @throws APIException
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);
330 rdf = env.newDataFactory(cls);
336 public void activity(History history, String header) {
337 if (history.getItem().isEmpty()) {
338 int start = header.indexOf('[');
340 pw().println("No Activity Found for " + header.substring(start));
343 pw().println(header);
344 for (int i=0;i<lineLength;++i)pw().print('-');
347 pw().format(hformat,"Date","Table","User","Memo");
348 for (int i=0;i<lineLength;++i)pw().print('-');
351 // Save Server time by Sorting locally
352 List<Item> items = history.getItem();
353 java.util.Collections.sort(items, new Comparator<Item>() {
355 public int compare(Item o1, Item o2) {
356 return o2.getTimestamp().compare(o1.getTimestamp());
360 for (History.Item item : items) {
361 GregorianCalendar gc = item.getTimestamp().toGregorianCalendar();
363 dateFmt.format(gc.getTime()),
372 * Turn String Array into a | delimited String
376 public static String optionsToString(String[] options) {
377 StringBuilder sb = new StringBuilder();
378 boolean first = true;
379 for (String s : options) {
387 return sb.toString();
391 * return which index number the Option matches.
393 * throws an Exception if not part of this Option Set
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)) {
406 throw new CadiException(build(new StringBuilder("Invalid Option: "),null).toString());
409 // protected RosettaEnv env() {
410 // return aafcli.env;
413 protected HMangr hman() {
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;
424 RET ret = aafcli.hman.same(aafcli.ss,retryable);
426 // Store last call in AAFcli, because Cmds are all different instances.
427 aafcli.prevCall = retryable;
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);
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);
441 protected PrintWriter pw() {
445 public String getName() {
449 public void reportHead(String ... str) {
451 boolean first = true;
453 for (String s : str) {
471 public String reportColHead(String format, String ... args) {
472 pw().format(format,(Object[])args);
477 public void reportLine() {
478 for (int i=0;i<lineLength;++i)pw().print('-');
482 protected void setQueryParamsOn(Rcli<?> rcli) {
483 StringBuilder sb=null;
485 if ((force=aafcli.forceString())!=null) {
486 sb = new StringBuilder("force=");
489 if (aafcli.addRequest()) {
491 sb = new StringBuilder("future=true");
493 sb.append("&future=true");
496 if (sb!=null && rcli!=null) {
497 rcli.setQueryParams(sb.toString());
502 // * If Force is set, will return True once only, then revert to "FALSE".
506 // protected String checkForce() {
507 // if (TRUE.equalsIgnoreCase(env.getProperty(FORCE, FALSE))) {
508 // env.setProperty(FORCE, FALSE);
514 public String toString() {
515 StringBuilder sb = new StringBuilder();
516 if (parent==null) { // ultimate parent
518 return sb.toString();
520 return parent.toString();
524 // private String getOrgRealm() {
529 * Appends shortID with Realm, but only when allowed by Organization
530 * @throws OrganizationException
532 public String fullID(String id) {
534 if (id.indexOf('@') < 0) {
537 return id; // is already a full ID