1 /*******************************************************************************
\r
2 * ============LICENSE_START====================================================
\r
4 * * ===========================================================================
\r
5 * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
\r
6 * * Copyright © 2017 Amdocs
\r
7 * * ===========================================================================
\r
8 * * Licensed under the Apache License, Version 2.0 (the "License");
\r
9 * * you may not use this file except in compliance with the License.
\r
10 * * You may obtain a copy of the License at
\r
12 * * http://www.apache.org/licenses/LICENSE-2.0
\r
14 * * Unless required by applicable law or agreed to in writing, software
\r
15 * * distributed under the License is distributed on an "AS IS" BASIS,
\r
16 * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
17 * * See the License for the specific language governing permissions and
\r
18 * * limitations under the License.
\r
19 * * ============LICENSE_END====================================================
\r
21 * * ECOMP is a trademark and service mark of AT&T Intellectual Property.
\r
23 ******************************************************************************/
\r
24 package com.att.cmd;
\r
26 import java.io.PrintWriter;
\r
27 import java.io.StringReader;
\r
28 import java.sql.Date;
\r
29 import java.text.DateFormat;
\r
30 import java.text.SimpleDateFormat;
\r
31 import java.util.ArrayList;
\r
32 import java.util.Comparator;
\r
33 import java.util.GregorianCalendar;
\r
34 import java.util.List;
\r
35 import java.util.Stack;
\r
36 import java.util.concurrent.ConcurrentHashMap;
\r
38 import com.att.cadi.CadiException;
\r
39 import com.att.cadi.LocatorException;
\r
40 import com.att.cadi.client.Future;
\r
41 import com.att.cadi.client.Rcli;
\r
42 import com.att.cadi.client.Retryable;
\r
43 import com.att.cadi.http.HMangr;
\r
44 import com.att.cssa.rserv.HttpMethods;
\r
45 import com.att.inno.env.APIException;
\r
46 import com.att.inno.env.Data.TYPE;
\r
47 import com.att.inno.env.Env;
\r
48 import com.att.inno.env.util.Chrono;
\r
49 import com.att.rosetta.env.RosettaDF;
\r
50 import com.att.rosetta.env.RosettaEnv;
\r
52 import aaf.v2_0.Error;
\r
53 import aaf.v2_0.History;
\r
54 import aaf.v2_0.History.Item;
\r
55 import aaf.v2_0.Request;
\r
58 public abstract class Cmd {
\r
59 private static final String AAF_DEFAULT_REALM = "aaf_default_realm";
\r
61 private static final DateFormat dateFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
\r
62 protected static final String BLANK = "";
\r
63 protected static final String COMMA = ","; // for use in splits
\r
65 protected static final int lineLength = 80;
\r
67 private final static String hformat = "%-23s %-5s %-20s %-35s\n";
\r
69 public static final String STARTDATE = "startdate";
\r
70 public static final String ENDDATE = "enddate";
\r
72 private String name;
\r
73 private final Param[] params;
\r
74 private int required;
\r
75 protected final Cmd parent;
\r
76 protected final List<Cmd> children;
\r
77 private final ConcurrentHashMap<Class<?>,RosettaDF<?>> dfs = new ConcurrentHashMap<Class<?>,RosettaDF<?>>();
\r
78 public final AAFcli aafcli;
\r
81 public Cmd(AAFcli aafcli, String name, Param ... params) {
\r
82 this(aafcli,null, name,params);
\r
85 public Cmd(Cmd parent, String name, Param ... params) {
\r
86 this(parent.aafcli,parent, name,params);
\r
89 Cmd(AAFcli aafcli, Cmd parent, String name, Param ... params) {
\r
90 this.parent = parent;
\r
91 this.aafcli = aafcli;
\r
92 this.env = aafcli.env;
\r
94 parent.children.add(this);
\r
96 children = new ArrayList<Cmd>();
\r
97 this.params = params;
\r
100 for(Param p : params) {
\r
107 public final int exec(int idx, String ... args) throws CadiException, APIException, LocatorException {
\r
108 if(args.length-idx<required) {
\r
109 throw new CadiException(build(new StringBuilder("Too few args: "),null).toString());
\r
111 return _exec(idx,args);
\r
114 protected abstract int _exec(int idx, final String ... args) throws CadiException, APIException, LocatorException;
\r
116 public void detailedHelp(int indent,StringBuilder sb) {
\r
119 protected void detailLine(StringBuilder sb, int length, String s) {
\r
120 multiChar(sb,length,' ',0);
\r
124 public void apis(int indent,StringBuilder sb) {
\r
127 protected void api(StringBuilder sb, int _indent, HttpMethods hmeth, String pathInfo, Class<?> cls,boolean head) {
\r
128 int indent = _indent;
\r
129 final String meth = hmeth.name();
\r
132 detailLine(sb,indent,"APIs:");
\r
135 multiChar(sb,indent,' ',0);
\r
138 sb.append(pathInfo);
\r
139 String cliString = aafcli.typeString(cls,true);
\r
140 if(indent+meth.length()+pathInfo.length()+cliString.length()+2>80) {
\r
142 multiChar(sb,indent+3+meth.length(),' ',0);
\r
143 } else { // same line
\r
146 sb.append(cliString);
\r
149 protected void multiChar(StringBuilder sb, int length, char c, int indent) {
\r
151 for(int i=0;i<indent;++i)sb.append(' ');
\r
152 for(int i=indent;i<length;++i)sb.append(c);
\r
155 public StringBuilder build(StringBuilder sb, StringBuilder detail) {
\r
160 int line = sb.lastIndexOf("\n")+1;
\r
164 int indent = sb.length()-line;
\r
165 for(Param p : params) {
\r
166 sb.append(p.required?'<':'[');
\r
168 sb.append(p.required?"> ": "] ");
\r
171 boolean first = true;
\r
172 for(Cmd child : children) {
\r
175 } else if(detail==null) {
\r
176 multiChar(sb,indent,' ',0);
\r
178 // Write parents for Detailed Report
\r
179 Stack<String> stack = new Stack<String>();
\r
180 for(Cmd c = child.parent;c!=null;c=c.parent) {
\r
182 stack.push(c.name);
\r
185 if(!stack.isEmpty()) {
\r
187 while(!stack.isEmpty()) {
\r
188 sb.append(stack.pop());
\r
193 child.build(sb,detail);
\r
195 child.detailedHelp(4, detail);
\r
196 // If Child wrote something, then add, bracketing by lines
\r
197 if(detail.length()>0) {
\r
198 multiChar(sb,80,'-',2);
\r
201 multiChar(sb,80,'-',2);
\r
203 detail.setLength(0); // reuse
\r
212 protected void error(Future<?> future) {
\r
213 StringBuilder sb = new StringBuilder("Failed");
\r
214 String desc = future.body();
\r
215 int code = future.code();
\r
216 if(desc==null || desc.length()==0) {
\r
218 } else if(desc.startsWith("{")) {
\r
219 StringReader sr = new StringReader(desc);
\r
221 // Note: 11-18-2013. This rather convoluted Message Structure required by TSS Restful Specs, reflecting "Northbound" practices.
\r
222 Error err = getDF(Error.class).newData().in(TYPE.JSON).load(sr).asObject();
\r
224 sb.append(err.getMessageId());
\r
226 String messageBody = err.getText();
\r
227 List<String> vars = err.getVariables();
\r
229 for (int varCounter=0;varCounter<vars.size();) {
\r
230 String var = vars.get(varCounter);
\r
232 if (messageBody.indexOf("%" + varCounter) >= 0) {
\r
233 if((pipe = var.indexOf('|'))>=0) { // In AAF, we use a PIPE for Choice
\r
234 if (aafcli.isTest()) {
\r
235 String expiresStr = var.substring(pipe);
\r
236 var = var.replace(expiresStr, "[Placeholder]");
\r
238 StringBuilder varsb = new StringBuilder(var);
\r
239 varsb.deleteCharAt(pipe);
\r
240 var = varsb.toString();
\r
242 messageBody = messageBody.replace("%" + varCounter, varCounter-1 + ") " + var);
\r
244 messageBody = messageBody.replace("%" + varCounter, var);
\r
248 sb.append(messageBody);
\r
249 } catch (Exception e) {
\r
251 sb.append(" (Note: Details cannot be obtained from Error Structure)");
\r
253 } else if(desc.startsWith("<html>")){ // Core Jetty, etc sends HTML for Browsers
\r
256 sb.append(" with code ");
\r
265 private void withCode(StringBuilder sb, Integer code) {
\r
266 sb.append(" with code ");
\r
270 sb.append(" (HTTP Not Authenticated)");
\r
273 sb.append(" (HTTP Forbidden)");
\r
276 sb.append(" (HTTP Not Found)");
\r
283 * Consistently set start and end dates from Requests (all derived from Request)
\r
286 protected void setStartEnd(Request req) {
\r
287 // Set Start/End Dates, if exist
\r
289 if((str = env.getProperty(Cmd.STARTDATE,null))!=null) {
\r
290 req.setStart(Chrono.timeStamp(Date.valueOf(str)));
\r
293 if((str = env.getProperty(Cmd.ENDDATE,null))!=null) {
\r
294 req.setEnd(Chrono.timeStamp(Date.valueOf(str)));
\r
298 @SuppressWarnings("unchecked")
\r
299 protected<T> RosettaDF<T> getDF(Class<T> cls) throws APIException {
\r
300 RosettaDF<T> rdf = (RosettaDF<T>)dfs.get(cls);
\r
302 rdf = env().newDataFactory(cls);
\r
308 public void activity(History history, String header) {
\r
309 if (history.getItem().isEmpty()) {
\r
310 int start = header.indexOf('[');
\r
312 pw().println("No Activity Found for " + header.substring(start));
\r
315 pw().println(header);
\r
316 for(int i=0;i<lineLength;++i)pw().print('-');
\r
319 pw().format(hformat,"Date","Table","User","Memo");
\r
320 for(int i=0;i<lineLength;++i)pw().print('-');
\r
323 // Save Server time by Sorting locally
\r
324 List<Item> items = history.getItem();
\r
325 java.util.Collections.sort(items, new Comparator<Item>() {
\r
327 public int compare(Item o1, Item o2) {
\r
328 return o2.getTimestamp().compare(o1.getTimestamp());
\r
332 for(History.Item item : items) {
\r
333 GregorianCalendar gc = item.getTimestamp().toGregorianCalendar();
\r
334 pw().format(hformat,
\r
335 dateFmt.format(gc.getTime()),
\r
344 * Turn String Array into a | delimited String
\r
348 public static String optionsToString(String[] options) {
\r
349 StringBuilder sb = new StringBuilder();
\r
350 boolean first = true;
\r
351 for(String s : options) {
\r
359 return sb.toString();
\r
363 * return which index number the Option matches.
\r
365 * throws an Exception if not part of this Option Set
\r
370 * @throws Exception
\r
372 public int whichOption(String[] options, String test) throws CadiException {
\r
373 for(int i=0;i<options.length;++i) {
\r
374 if(options[i].equals(test)) {
\r
378 throw new CadiException(build(new StringBuilder("Invalid Option: "),null).toString());
\r
381 protected RosettaEnv env() {
\r
385 protected HMangr hman() {
\r
386 return aafcli.hman;
\r
389 public<RET> RET same(Retryable<RET> retryable) throws APIException, CadiException, LocatorException {
\r
390 // We're storing in AAFCli, because we know it's always the same, and single threaded
\r
391 if(aafcli.prevCall!=null) {
\r
392 retryable.item(aafcli.prevCall.item());
\r
393 retryable.lastClient=aafcli.prevCall.lastClient;
\r
396 RET ret = aafcli.hman.same(aafcli.ss,retryable);
\r
398 // Store last call in AAFcli, because Cmds are all different instances.
\r
399 aafcli.prevCall = retryable;
\r
403 public<RET> RET all(Retryable<RET> retryable) throws APIException, CadiException, LocatorException {
\r
404 this.setQueryParamsOn(retryable.lastClient);
\r
405 return aafcli.hman.all(aafcli.ss,retryable);
\r
408 public<RET> RET oneOf(Retryable<RET> retryable,String host) throws APIException, CadiException, LocatorException {
\r
409 this.setQueryParamsOn(retryable.lastClient);
\r
410 return aafcli.hman.oneOf(aafcli.ss,retryable,true,host);
\r
413 protected PrintWriter pw() {
\r
417 public String getName() {
\r
421 public void reportHead(String ... str) {
\r
423 boolean first = true;
\r
425 for(String s : str) {
\r
443 public String reportColHead(String format, String ... args) {
\r
444 pw().format(format,(Object[])args);
\r
449 public void reportLine() {
\r
450 for(int i=0;i<lineLength;++i)pw().print('-');
\r
454 protected void setQueryParamsOn(Rcli<?> rcli) {
\r
455 StringBuilder sb=null;
\r
457 if((force=aafcli.forceString())!=null) {
\r
458 sb = new StringBuilder("force=");
\r
461 if(aafcli.addRequest()) {
\r
463 sb = new StringBuilder("request=true");
\r
465 sb.append("&request=true");
\r
468 if(sb!=null && rcli!=null) {
\r
469 rcli.setQueryParams(sb.toString());
\r
474 // * If Force is set, will return True once only, then revert to "FALSE".
\r
478 // protected String checkForce() {
\r
479 // if(TRUE.equalsIgnoreCase(env.getProperty(FORCE, FALSE))) {
\r
480 // env.setProperty(FORCE, FALSE);
\r
486 public String toString() {
\r
487 StringBuilder sb = new StringBuilder();
\r
488 if(parent==null) { // ultimate parent
\r
490 return sb.toString();
\r
492 return parent.toString();
\r
496 public String getOrgRealm() {
\r
497 return env.getProperty(AAF_DEFAULT_REALM);
\r