1 package emissary.parser;
2
3 import jakarta.annotation.Nullable;
4 import org.apache.commons.collections4.CollectionUtils;
5 import org.slf4j.Logger;
6 import org.slf4j.LoggerFactory;
7
8 import java.util.ArrayList;
9 import java.util.HashMap;
10 import java.util.List;
11 import java.util.Map;
12
13 /**
14 * Detailed session information as it is parsed This class just records offsets and length of various things using lists
15 * of PositionRecord. If you want to actually produce the bytes of a session see SessionParser.decomposeSession and
16 * emissary.parser.DecomposedSession
17 * <p>
18 * No assertions are made about the order of the header, footer, and data sections within the original byte array, it
19 * can be constructed from position records in any order, possibly overlapping and repeated, We only assert that the
20 * sections asked for are not out of bounds with respect to the overall array boundaries.
21 * <p>
22 * The validation scheme allows the overall bounds to be set at either the beginning or at the end of session parsing
23 * without performance penalty either way.
24 */
25 public class InputSession {
26
27 // Logger
28 private static final Logger logger = LoggerFactory.getLogger(InputSession.class);
29
30 // Overall start/length of the session
31 @Nullable
32 protected PositionRecord overall = null;
33
34 // ordered list of PositionRecord for the header within the session
35 protected List<PositionRecord> header = new ArrayList<>();
36
37 // ordered list of PositionRecord for the footer within the session
38 protected List<PositionRecord> footer = new ArrayList<>();
39
40 // ordered list of PositionRecord for the data within the session
41 protected List<PositionRecord> data = new ArrayList<>();
42
43 // unordered collected metadata during parsing
44 protected Map<String, Object> metaData = new HashMap<>();
45
46 // Indicator of validness
47 protected boolean valid = true;
48
49 /**
50 * Record details of an input session
51 */
52 public InputSession() {}
53
54 /**
55 * Record details of an input session
56 *
57 * @param o the overall position record
58 */
59 public InputSession(PositionRecord o) {
60 this.overall = o;
61 }
62
63 /**
64 * Record details of an input session
65 *
66 * @param o the overall position record
67 * @param d position records for the data
68 */
69 public InputSession(PositionRecord o, List<PositionRecord> d) throws ParserException {
70 this(o);
71 addDataRecs(d);
72 }
73
74 /**
75 * Record details of an input session
76 *
77 * @param o the overall position record
78 * @param d position record for the data
79 */
80 public InputSession(PositionRecord o, PositionRecord d) throws ParserException {
81 this(o);
82 addDataRec(d);
83 }
84
85 /**
86 * Record details of an input session
87 *
88 * @param o the overall position record
89 * @param h position records for the header
90 * @param f position records for the footer
91 * @param d position records for the data
92 */
93 public InputSession(PositionRecord o, List<PositionRecord> h, List<PositionRecord> f, List<PositionRecord> d) throws ParserException {
94 this(o, d);
95 addHeaderRecs(h);
96 addFooterRecs(f);
97 }
98
99 /**
100 * Record details of an input session
101 *
102 * @param o the overall position record
103 * @param h position record for the header
104 * @param f position record for the footer
105 * @param d position record for the data
106 * @param m map of collected metadata
107 */
108 public InputSession(PositionRecord o, List<PositionRecord> h, List<PositionRecord> f, List<PositionRecord> d, Map<String, Object> m)
109 throws ParserException {
110 this(o, h, f, d);
111 addMetaData(m);
112 }
113
114 /**
115 * Set the overall position record
116 *
117 * @param rec the PositionRecord for the overall session range
118 */
119 public void setOverall(PositionRecord rec) throws ParserException {
120 this.overall = rec;
121 validateAll();
122 }
123
124 /**
125 * Set the overall position record from the data
126 *
127 * @param start for the position record
128 * @param length for the position record
129 */
130 public void setOverall(int start, int length) throws ParserException {
131 setOverall(new PositionRecord(start, length));
132 }
133
134 /**
135 * Add a map of metadata
136 *
137 * @param m map of String key and String or PositionRecord values
138 */
139 public void addMetaData(Map<String, Object> m) throws ParserException {
140 for (Map.Entry<String, Object> entry : m.entrySet()) {
141 String key = entry.getKey();
142 Object val = entry.getValue();
143 if (val instanceof PositionRecord) {
144 validateRecord((PositionRecord) val);
145 metaData.put(key, val);
146 } else if (val instanceof String) {
147 metaData.put(key, val);
148 } else {
149 logger.warn("Ignoring metadata record named {} with type of {} - it is not a PositionRecord or a String", key,
150 val.getClass().getName());
151 }
152 }
153 }
154
155 /**
156 * Set the session validity
157 *
158 * @param b true if session is valid
159 */
160 public void setValid(boolean b) {
161 this.valid = b;
162 }
163
164 /**
165 * Get session validity
166 *
167 * @return true if session is valid
168 */
169 public boolean isValid() {
170 return valid;
171 }
172
173 /**
174 * Get overall start position
175 *
176 * @return overall start
177 */
178 public long getStart() {
179 if (overall != null) {
180 return overall.getPosition();
181 }
182 return 0;
183 }
184
185 /**
186 * Get overall length
187 *
188 * @return overall length
189 */
190 public long getLength() {
191 if (overall != null) {
192 return overall.getLength();
193 }
194 return 0;
195 }
196
197 /**
198 * Get overall position record for the session
199 *
200 * @return overall position record
201 */
202 public PositionRecord getOverall() {
203 return overall;
204 }
205
206 /**
207 * Add and validate a list of header records
208 *
209 * @param h list of PositionRecord
210 * @throws ParserException when a record is out of bounds
211 */
212 public void addHeaderRecs(@Nullable List<PositionRecord> h) throws ParserException {
213 if (CollectionUtils.isNotEmpty(h)) {
214 validateList(h);
215 header.addAll(h);
216 }
217 }
218
219 /**
220 * Add and validate a list of data records
221 *
222 * @param d list of PositionRecord
223 * @throws ParserException when a record is out of bounds
224 */
225 public void addDataRecs(@Nullable List<PositionRecord> d) throws ParserException {
226 if (CollectionUtils.isNotEmpty(d)) {
227 validateList(d);
228 data.addAll(d);
229 }
230 }
231
232 /**
233 * Add and validate a list of footer records
234 *
235 * @param f list of PositionRecord
236 * @throws ParserException when a record is out of bounds
237 */
238 public void addFooterRecs(@Nullable List<PositionRecord> f) throws ParserException {
239 if (CollectionUtils.isNotEmpty(f)) {
240 validateList(f);
241 footer.addAll(f);
242 }
243 }
244
245 /**
246 * Add and validate a header record
247 *
248 * @param r the record to add
249 * @throws ParserException when a record is out of bounds
250 */
251 public void addHeaderRec(PositionRecord r) throws ParserException {
252 validateRecord(r);
253 header.add(r);
254 }
255
256 /**
257 * Create a position record and add it to the header
258 *
259 * @param pos starting position
260 * @param len length of data
261 * @throws ParserException when out of bounds
262 */
263 public void addHeaderRec(int pos, int len) throws ParserException {
264 addHeaderRec(new PositionRecord(pos, len));
265 }
266
267 /**
268 * Add and validate a footer record
269 *
270 * @param r the record to add
271 * @throws ParserException when a record is out of bounds
272 */
273 public void addFooterRec(PositionRecord r) throws ParserException {
274 validateRecord(r);
275 footer.add(r);
276 }
277
278 /**
279 * Create a position record and add it to the footer
280 *
281 * @param pos starting position
282 * @param len length of data
283 * @throws ParserException when out of bounds
284 */
285 public void addFooterRec(int pos, int len) throws ParserException {
286 addFooterRec(new PositionRecord(pos, len));
287 }
288
289 /**
290 * Add a validate a data record
291 *
292 * @param r the record to add
293 * @throws ParserException when a record is out of bounds
294 */
295 public void addDataRec(PositionRecord r) throws ParserException {
296 validateRecord(r);
297 data.add(r);
298 }
299
300 /**
301 * Create a position record and add it to the data
302 *
303 * @param pos starting position
304 * @param len length of data
305 * @throws ParserException when out of bounds
306 */
307 public void addDataRec(int pos, int len) throws ParserException {
308 addDataRec(new PositionRecord(pos, len));
309 }
310
311 /**
312 * Add a metadata position record
313 *
314 * @param name name of metadata item
315 * @param r position record of data
316 * @throws ParserException when out of bounds
317 */
318 public void addMetaDataRec(String name, PositionRecord r) throws ParserException {
319 validateRecord(r);
320 metaData.put(name, r);
321 }
322
323 /**
324 * Add a metadata position record
325 *
326 * @param name name of metadata item
327 * @param rec string value of metadata item
328 */
329 public void addMetaDataRec(String name, String rec) {
330 metaData.put(name, rec);
331 }
332
333 /**
334 * Count of data position records
335 *
336 * @return count
337 */
338 public int getDataCount() {
339 return data.size();
340 }
341
342 /**
343 * Count of footer position records
344 *
345 * @return count
346 */
347 public int getFooterCount() {
348 return footer.size();
349 }
350
351 /**
352 * Count of header position records
353 *
354 * @return count
355 */
356 public int getHeaderCount() {
357 return header.size();
358 }
359
360 /**
361 * Count of metadata records
362 *
363 * @return count
364 */
365 public int getMetaDataCount() {
366 return metaData.size();
367 }
368
369 /**
370 * Get footer position records
371 *
372 * @return list of PositionRecord
373 */
374 public List<PositionRecord> getFooter() {
375 return footer;
376 }
377
378 /**
379 * List header records
380 *
381 * @return list of PositionRecord
382 */
383 public List<PositionRecord> getHeader() {
384 return header;
385 }
386
387 /**
388 * Get data position records
389 *
390 * @return list of PositionRecord
391 */
392 public List<PositionRecord> getData() {
393 return data;
394 }
395
396 /**
397 * Return a map of metadata information. Some values will be Strings, others will be PositionRecords
398 *
399 * @return metadata
400 */
401 public Map<String, Object> getMetaData() {
402 return metaData;
403 }
404
405 /**
406 * Info pump for debugging output
407 *
408 * @return string representation
409 */
410 @Override
411 public String toString() {
412 StringBuilder sb = new StringBuilder();
413 sb.append("Session is ").append(this.isValid() ? "" : "not").append("valid").append("\n");
414 sb.append("Session overall ").append(this.getOverall()).append("\n");
415 sb.append("Header record ").append(this.getHeader()).append("\n");
416 sb.append("Data record ").append(this.getData()).append("\n");
417 sb.append("Footer record ").append(this.getFooter()).append("\n");
418 Map<String, Object> m = this.getMetaData();
419 sb.append("Metadata count ").append(m == null ? 0 : m.size()).append("\n");
420 return sb.toString();
421 }
422
423 /**
424 * Validation of one PositionRecord. Does nothing if overall bounds not yet set
425 *
426 * @param r the record to check
427 * @throws ParserException when out of bounds
428 */
429 protected void validateRecord(PositionRecord r) throws ParserException {
430 if (overall != null && r != null && ((r.getPosition() < overall.getPosition()) || (r.getEnd() > overall.getEnd()))) {
431 throw new ParserException("Position record " + r + " is out of bounds for the data " + overall);
432 }
433 }
434
435 /**
436 * Validate a list of PositionRecord. Does nothing if overall bounds not yet set
437 *
438 * @param list the list of PositionRecord
439 * @throws ParserException when out of bounds
440 */
441 protected void validateList(@Nullable List<PositionRecord> list) throws ParserException {
442
443 if (overall == null || list == null) {
444 return;
445 }
446
447 for (PositionRecord p : list) {
448 validateRecord(p);
449 }
450 }
451
452 /**
453 * Validate everything. Does nothing if overall bounds not yet set
454 *
455 * @throws ParserException when out of bounds
456 */
457 protected void validateAll() throws ParserException {
458 if (overall == null) {
459 return;
460 }
461 validateList(header);
462 validateList(footer);
463 validateList(data);
464 validateMetaData();
465 }
466
467 /**
468 * Validate the PositionRecords in the metadata map Does nothing if overall bounds not yet set
469 *
470 * @throws ParserException when out of bounds
471 */
472 protected void validateMetaData() throws ParserException {
473 if (overall == null || metaData.isEmpty()) {
474 return;
475 }
476
477 for (Object obj : metaData.values()) {
478 if (obj instanceof PositionRecord) {
479 validateRecord((PositionRecord) obj);
480 }
481 }
482 }
483
484 }