[AAF-21] Initial code import
[aaf/authz.git] / authz-core / src / main / java / com / att / cssa / rserv / TypedCode.java
1 /*******************************************************************************\r
2  * ============LICENSE_START====================================================\r
3  * * org.onap.aai\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
11  * * \r
12  *  *      http://www.apache.org/licenses/LICENSE-2.0\r
13  * * \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
20  * *\r
21  * * ECOMP is a trademark and service mark of AT&T Intellectual Property.\r
22  * *\r
23  ******************************************************************************/\r
24 package com.att.cssa.rserv;\r
25 \r
26 import java.io.IOException;\r
27 import java.util.ArrayList;\r
28 import java.util.HashMap;\r
29 import java.util.List;\r
30 \r
31 import javax.servlet.ServletException;\r
32 \r
33 import com.att.inno.env.Env;\r
34 import com.att.inno.env.TimeTaken;\r
35 import com.att.inno.env.Trans;\r
36 \r
37 \r
38 /**\r
39  * TypedCode organizes implementation code based on the Type and Version of code it works with so that it can\r
40  * be located quickly at runtime based on the "Accept" HTTP Header.\r
41  *\r
42  * FYI: For those in the future wondering why I would create a specialized set of "Pair" for the data content:\r
43  *   1) TypeCode is used in Route, and this code is used for every transaction... it needs to be blazingly fast\r
44  *   2) The actual number of objects accessed is quite small and built at startup.  Arrays are best\r
45  *   3) I needed a small, well defined tree where each level is a different Type.  Using a "Pair" Generic definitions, \r
46  *      I created type-safety at each level, which you can't get from a TreeSet, etc.\r
47  *   4) Chaining through the Network is simply object dereferencing, which is as fast as Java can go.\r
48  *   5) The drawback is that in your code is that all the variables are named "x" and "y", which can be a bit hard to\r
49  *      read both in code, and in the debugger.  However, TypeSafety allows your IDE (Eclipse) to help you make the \r
50  *      choices.  Also, make sure you have a good "toString()" method on each object so you can see what's happening\r
51  *      in the IDE Debugger.\r
52  *   \r
53  * Empirically, this method of obtaining routes proved to be much faster than the HashSet implementations available in otherwise\r
54  * competent Open Source.\r
55  *\r
56  * @param <TRANS>\r
57  */\r
58 public class TypedCode<TRANS extends Trans> extends Content<TRANS> {\r
59                 private List<Pair<String, Pair<HttpCode<TRANS,?>,List<Pair<String, Object>>>>> types;\r
60 \r
61                 public TypedCode() {\r
62                         types = new ArrayList<Pair<String,Pair<HttpCode<TRANS,?>,List<Pair<String,Object>>>>>();\r
63                 }\r
64                 \r
65                 /**\r
66                  * Construct Typed Code based on ContentType parameters passed in\r
67                  * \r
68                  * @param code\r
69                  * @param others\r
70                  * @return\r
71                  */\r
72                 public TypedCode<TRANS> add(HttpCode<TRANS,?> code, String ... others) {\r
73                         StringBuilder sb = new StringBuilder();\r
74                         boolean first = true;\r
75                         for(String str : others) {\r
76                                 if(first) {\r
77                                         first = false; \r
78                                 } else {\r
79                                         sb.append(',');\r
80                                 }\r
81                                 sb.append(str);\r
82                         }\r
83                         parse(code, sb.toString());\r
84                         \r
85                         return this;\r
86                 }\r
87                 \r
88                 @Override\r
89                 protected Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>> types(HttpCode<TRANS,?> code, String str) {\r
90                         Pair<String, Pair<HttpCode<TRANS,?>,List<Pair<String, Object>>>> type = null;\r
91                         ArrayList<Pair<String, Object>> props = new ArrayList<Pair<String,Object>>();\r
92                         // Want Q percentage is to be first in the array everytime.  If not listed, 1.0 is default\r
93                         props.add(new Pair<String,Object>(Q,1f));\r
94                         Pair<HttpCode<TRANS,?>, List<Pair<String,Object>>> cl = new Pair<HttpCode<TRANS,?>, List<Pair<String,Object>>>(code, props);\r
95 //                      // breakup "plus" stuff, i.e. application/xaml+xml\r
96 //                      int plus = str.indexOf('+');\r
97 //                      if(plus<0) {\r
98                                 type = new Pair<String, Pair<HttpCode<TRANS,?>,List<Pair<String,Object>>>>(str, cl);\r
99                                 types.add(type);\r
100                                 return type;\r
101 //                      } else {\r
102 //                              int prev = str.indexOf('/')+1;\r
103 //                              String first = str.substring(0,prev);\r
104 //                              String nstr;\r
105 //                              while(prev!=0) {\r
106 //                                      nstr = first + (plus>-1?str.substring(prev,plus):str.substring(prev));\r
107 //                                      type = new Pair<String, Pair<HttpCode<TRANS,?>,List<Pair<String,Object>>>>(nstr, cl);\r
108 //                                      types.add(type);\r
109 //                                      prev = plus+1;\r
110 //                                      plus = str.indexOf('+',prev);\r
111 //                              }\r
112 //                      return type;\r
113 //                      }\r
114                 }\r
115 \r
116                 @Override\r
117                 protected boolean props(Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>> type, String tag, String value) {\r
118                         if(tag.equals(Q)) { // reset the Q value (first in array)\r
119                                 boolean rv = true;\r
120                                 try {\r
121                                         type.y.y.get(0).y=Float.parseFloat(value);\r
122                                         return rv;\r
123                                 } catch (NumberFormatException e) {\r
124                                         rv=false; // Note: this awkward syntax forced by Sonar, which doesn't like doing nothing with Exception\r
125                                                           // which is what should happen\r
126                                 }\r
127                         }\r
128                         return type.y.y.add(new Pair<String,Object>(tag,"version".equals(tag)?new Version(value):value));\r
129                 }\r
130                 \r
131                 public Pair<String, Pair<HttpCode<TRANS, ?>, List<Pair<String, Object>>>> prep(TRANS trans, String compare) throws IOException, ServletException {\r
132                         Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>> c,rv=null;\r
133                         if(types.size()==1 && "".equals((c=types.get(0)).x)) { // if there are no checks for type, skip\r
134                                 rv = c;\r
135                         } else {\r
136                                 if(compare==null || compare.length()==0) {\r
137                                         rv = types.get(0); // first code is used\r
138                                 } else {\r
139                                         Acceptor<TRANS> acc = new Acceptor<TRANS>(types);\r
140                                         boolean accepted;\r
141                                         TimeTaken tt = trans.start(compare, Env.SUB);\r
142                                         try {\r
143                                                 accepted = acc.parse(null, compare);\r
144                                         } finally {\r
145                                                 tt.done();\r
146                                         }\r
147                                         if(accepted) {\r
148                                                 switch(acc.acceptable.size()) {\r
149                                                         case 0: \r
150 //                                                              // TODO best Status Code?\r
151 //                                                              resp.setStatus(HttpStatus.NOT_ACCEPTABLE_406);\r
152                                                                 break;\r
153                                                         case 1: \r
154                                                                 rv = acc.acceptable.get(0);\r
155                                                                 break;\r
156                                                         default: // compare Q values to get Best Match\r
157                                                                 float bestQ = -1.0f;\r
158                                                                 Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>> bestT = null;\r
159                                                                 for(Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>> type : acc.acceptable) {\r
160                                                                         Float f = (Float)type.y.y.get(0).y; // first property is always Q\r
161                                                                         if(f>bestQ) {\r
162                                                                                 bestQ=f;\r
163                                                                                 bestT = type;\r
164                                                                         }\r
165                                                                 }\r
166                                                                 if(bestT!=null) {\r
167                                                                         // When it is a GET, the matched type is what is returned, so set ContentType\r
168 //                                                                      if(isGet)resp.setContentType(bestT.x); // set ContentType of Code<TRANS,?>\r
169 //                                                                      rv = bestT.y.x;\r
170                                                                         rv = bestT;\r
171                                                                 }\r
172                                                 }\r
173                                         } else {\r
174                                                 trans.checkpoint("No Match found for Accept");\r
175                                         }\r
176                                 }\r
177                         }\r
178                         return rv;\r
179                 }\r
180                 \r
181                 /**\r
182                  * Print on String Builder content related to specific Code\r
183                  * \r
184                  * This is for Reporting and Debugging purposes, so the content is not cached.\r
185                  * \r
186                  * If code is "null", then all content is matched\r
187                  * \r
188                  * @param code\r
189                  * @return\r
190                  */\r
191                 public StringBuilder relatedTo(HttpCode<TRANS, ?> code, StringBuilder sb) {\r
192                         boolean first = true;\r
193                         for(Pair<String, Pair<HttpCode<TRANS, ?>, List<Pair<String, Object>>>> pair : types) {\r
194                                 if(code==null || pair.y.x == code) {\r
195                                         if(first) {\r
196                                                 first = false;\r
197                                         } else {\r
198                                                 sb.append(',');\r
199                                         }\r
200                                         sb.append(pair.x);\r
201                                         for(Pair<String,Object> prop : pair.y.y) {\r
202                                                 // Don't print "Q".  it's there for internal use, but it is only meaningful for "Accepts"\r
203                                                 if(!prop.x.equals(Q) || !prop.y.equals(1f) ) {\r
204                                                         sb.append(';');\r
205                                                         sb.append(prop.x);\r
206                                                         sb.append('=');\r
207                                                         sb.append(prop.y);\r
208                                                 }\r
209                                         }\r
210                                 }\r
211                         }\r
212                         return sb;\r
213                 }\r
214                 \r
215                 public List<Pair<String, Object>> getContent(HttpCode<TRANS,?> code) {\r
216                         for(Pair<String, Pair<HttpCode<TRANS, ?>, List<Pair<String, Object>>>> pair : types) {\r
217                                 if(pair.y.x == code) {\r
218                                         return pair.y.y;\r
219                                 }\r
220                         }\r
221                         return null;\r
222                 }\r
223         \r
224                 public String toString() {\r
225                         return relatedTo(null,new StringBuilder()).toString();\r
226                 }\r
227                 \r
228                 public void api(RouteReport tr) {\r
229                         // Need to build up a map, because Prop entries can be in several places.\r
230                         HashMap<HttpCode<?,?>,StringBuilder> psb = new HashMap<HttpCode<?,?>,StringBuilder>();\r
231                         StringBuilder temp;\r
232                         tr.desc = null;\r
233                         \r
234                         // Read through Code/TypeCode trees for all accepted Typecodes\r
235                         for(Pair<String, Pair<HttpCode<TRANS, ?>, List<Pair<String, Object>>>> tc : types) {\r
236                                 // If new, then it's new Code set, create prefix content\r
237                                 if((temp=psb.get(tc.y.x))==null) {\r
238                                         psb.put(tc.y.x,temp=new StringBuilder());\r
239                                         if(tr.desc==null) {\r
240                                                 tr.desc = tc.y.x.desc();\r
241                                         }\r
242                                 } else {\r
243                                         temp.append(',');\r
244                                 }\r
245                                 temp.append(tc.x);\r
246 \r
247                                 // add all properties\r
248                                 for(Pair<String, Object> props : tc.y.y) {\r
249                                         temp.append(';');\r
250                                         temp.append(props.x);\r
251                                         temp.append('=');\r
252                                         temp.append(props.y);\r
253                                 }\r
254                         }\r
255                         // Gather all ContentType possibilities for the same code together\r
256                         \r
257                         for(StringBuilder sb : psb.values()) {\r
258                                 tr.contextTypes.add(sb.toString());\r
259                         }\r
260                 }\r
261 \r
262                 public String first() {\r
263                         if(types.size()>0) {\r
264                                 return types.get(0).x;\r
265                         }\r
266                         return null;\r
267                 }\r
268                 \r
269         }\r