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