Initial OpenECOMP policy/engine commit
[policy/engine.git] / ECOMP-PDP / src / test / java / org / openecomp / policy / pdp / test / custom / TestBase.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ECOMP-PDP
4  * ================================================================================
5  * Copyright (C) 2017 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
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
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=========================================================
19  */
20
21 package org.openecomp.policy.pdp.test.custom;
22
23 import java.io.BufferedReader;
24 import java.io.ByteArrayInputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.io.OutputStream;
29 import java.net.HttpURLConnection;
30 import java.net.MalformedURLException;
31 import java.net.URL;
32 import java.nio.file.FileVisitResult;
33 import java.nio.file.Files;
34 import java.nio.file.Path;
35 import java.nio.file.Paths;
36 import java.nio.file.SimpleFileVisitor;
37 import java.nio.file.attribute.BasicFileAttributes;
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.regex.Matcher;
41 import java.util.regex.Pattern;
42
43 import org.apache.commons.cli.CommandLine;
44 import org.apache.commons.cli.GnuParser;
45 import org.apache.commons.cli.HelpFormatter;
46 import org.apache.commons.cli.Option;
47 import org.apache.commons.cli.Options;
48 import org.apache.commons.cli.ParseException;
49 import org.apache.commons.io.IOUtils;
50 import org.apache.commons.logging.Log;
51 import org.apache.commons.logging.LogFactory;
52 import org.apache.http.entity.ContentType;
53
54 import com.att.research.xacml.api.AttributeValue;
55 import com.att.research.xacml.api.DataType;
56 import com.att.research.xacml.api.DataTypeException;
57 import com.att.research.xacml.api.DataTypeFactory;
58 import com.att.research.xacml.api.Decision;
59 import com.att.research.xacml.api.Identifier;
60 import com.att.research.xacml.api.Request;
61 import com.att.research.xacml.api.RequestAttributes;
62 import com.att.research.xacml.api.Response;
63 import com.att.research.xacml.api.Result;
64 import com.att.research.xacml.api.pdp.PDPEngine;
65 import com.att.research.xacml.api.pdp.PDPEngineFactory;
66 import com.att.research.xacml.api.pdp.PDPException;
67 import com.att.research.xacml.api.pep.PEPException;
68 import com.att.research.xacml.std.IdentifierImpl;
69 import com.att.research.xacml.std.StdAttributeValue;
70 import com.att.research.xacml.std.StdMutableAttribute;
71 import com.att.research.xacml.std.StdMutableRequest;
72 import com.att.research.xacml.std.StdMutableRequestAttributes;
73 import com.att.research.xacml.std.dom.DOMRequest;
74 import com.att.research.xacml.std.dom.DOMResponse;
75 import com.att.research.xacml.std.dom.DOMStructureException;
76 import com.att.research.xacml.std.json.JSONRequest;
77 import com.att.research.xacml.std.json.JSONResponse;
78 import com.att.research.xacml.std.json.JSONStructureException;
79 import com.att.research.xacml.util.FactoryException;
80 import com.att.research.xacml.util.XACMLProperties;
81 import com.google.common.base.Splitter;
82 import com.google.common.collect.Lists;
83 import org.openecomp.policy.common.logging.flexlogger.FlexLogger; 
84 import org.openecomp.policy.common.logging.flexlogger.Logger; 
85
86 /**
87  * This is a base class for setting up a test environment. Using properties files, it contains the
88  * necessary information for 
89  * 1. defining and providing attributes
90  * 2. defining and instantiating the PDP engine
91  * 3. creating PEP requests and calling the PDP engine
92  * 
93  *
94  */
95 public class TestBase extends SimpleFileVisitor<Path> {
96         private static final Logger logger      = FlexLogger.getLogger(TestBase.class);
97         
98         public class HelpException extends Exception {
99                 private static final long serialVersionUID = 1L;
100                 
101         }
102         
103         /**
104          * This private class holds information for properties defined for attribute
105          * generation. The user can configure the properties file such that attributes
106          * can be automatically generated and added into each request.
107          * 
108          *
109          */
110         class Generator {
111                 Path file;
112                 InputStream is;
113                 BufferedReader reader;
114                 List<StdMutableAttribute> attributes = new ArrayList<StdMutableAttribute>();
115                 
116                 public Generator(Path path) {
117                         this.file = path;
118                 }
119
120                 /**
121                  * read - reads in the next line of data
122                  * 
123                  * @return      String - a line from the csv containing attribute data
124                  */
125                 public String   read() {
126                         String str = null;
127                         if (is == null) {
128                                 try {
129                                         is = Files.newInputStream(file);
130                                 } catch (IOException e) {
131                                         logger.error(e);
132                                         return null;
133                                 }
134                         }
135                         if (reader == null) {
136                                 reader = new BufferedReader(new InputStreamReader(this.is));
137                         }
138                         try {
139                                 str = reader.readLine();
140                                 if (str == null) {
141                                         //
142                                         // No more strings, close up
143                                         //
144                                         this.close();
145                                 }
146                                 if (logger.isDebugEnabled()) {
147                                         logger.debug(str);
148                                 }
149                         } catch (IOException e) {
150                                 logger.error(e);
151                         }
152                         return str;
153                 }
154                 
155                 public void     close() {
156                         if (this.reader != null) {
157                                 try {
158                                         this.reader.close();
159                                 } catch (IOException idontcare) {
160                                 } finally {
161                                         this.reader = null;
162                                         this.is = null;
163                                 }
164                         }
165                 }
166                 
167         }
168         
169         public static final String PROP_GENERATOR = "xacml.attribute.generator";
170         
171         public static final String OPTION_HELP = "help";
172         public static final String OPTION_TESTDIR = "dir";
173         public static final String OPTION_TESTREST = "rest";
174         public static final String OPTION_TESTURL = "url";
175         public static final String OPTION_TESTOUTPUT = "output";
176         public static final String OPTION_LOOP = "loop";
177         public static final String OPTION_TESTNUMBERS = "testNumbers";
178
179         public static final String DEFAULT_RESTURL = "https://localhost:8080/pdp/";     // Modified for test purpose. Port no. 8443 to 8080
180         
181         public static Options options = new Options();
182         static {
183                 options.addOption(new Option(OPTION_HELP, false, "Prints help."));
184                 options.addOption(new Option(OPTION_TESTDIR, true, "Directory path where all the test properties and data are located."));
185                 options.addOption(new Option(OPTION_TESTREST, false, "Test against RESTful PDP."));
186                 options.addOption(new Option(OPTION_TESTURL, true, "URL to the RESTful PDP. Default is " + DEFAULT_RESTURL));
187                 options.addOption(new Option(OPTION_TESTOUTPUT, true, "Specify a different location for dumping responses."));
188                 options.addOption(new Option(OPTION_LOOP, true, "Number of times to loop through the tests. Default is 1. A value of -1 runs indefinitely."));
189                 options.addOption(new Option(OPTION_TESTNUMBERS, true, "Comma-separated list of numbers found in the names of the test files to be run.  Numbers must exactly match the file name, e.g. '02'.  Used to limit testing to specific set of tests."));
190         }
191         
192         protected String directory = null;
193         protected Path output = null;
194         protected boolean isREST;
195         protected URL restURL = null;
196         protected int loop = 1;
197         protected PDPEngine engine = null;
198         protected List<Generator> generators = new ArrayList<Generator>();
199         protected static DataTypeFactory dataTypeFactory                = null;
200         
201         private long    permits = 0;
202         private long    denies = 0;
203         private long    notapplicables = 0;
204         private long    indeterminates = 0;
205         
206         private long    expectedPermits = 0;
207         private long    expectedDenies = 0;
208         private long    expectedNotApplicables = 0;
209         private long    expectedIndeterminates = 0;
210         
211         private long    generatedpermits = 0;
212         private long    generateddenies = 0;
213         private long    generatednotapplicables = 0;
214         private long    generatedindeterminates = 0;
215         
216         private long    responseMatches = 0;
217         private long    responseNotMatches = 0;
218         
219         private String[]        testNumbersArray = null;
220         
221         protected final Pattern pattern = Pattern.compile("Request[.]\\d+[.](Permit|Deny|NA|Indeterminate|Generate|Unknown)\\.(json|xml)");
222         
223         public static boolean isJSON(Path file) {
224                 return file.toString().endsWith(".json");
225         }
226         
227         public static boolean isXML(Path file) {
228                 return file.toString().endsWith(".xml");
229         }
230         
231         public TestBase(String[] args) throws ParseException, MalformedURLException, HelpException {
232                 //
233                 // Finish Initialization
234                 //
235                 this.restURL = new URL(DEFAULT_RESTURL);
236                 //
237                 // Parse arguments
238                 //
239                 this.parseCommands(args);
240         }
241         
242         /**
243          * Parse in the command line arguments that the following parameters:
244          * 
245          * @param args - command line arguments
246          * @throws ParseException 
247          * @throws MalformedURLException 
248          * @throws HelpException 
249          */
250         protected void parseCommands(String[] args) throws ParseException, MalformedURLException, HelpException {
251                 //
252                 // Parse the command line options
253                 //
254                 CommandLine cl;
255                 cl = new GnuParser().parse(options, args);
256                 //
257                 // Check for what we have
258                 //
259                 if (cl.hasOption(OPTION_HELP)) {
260                 new HelpFormatter().printHelp("Usage: -dir testdirectory OPTIONS",
261                                 options);
262                 throw new HelpException();
263                 }
264                 if (cl.hasOption(OPTION_TESTDIR)) {
265                         this.directory = cl.getOptionValue(OPTION_TESTDIR);
266                 } else {
267                         throw new IllegalArgumentException("You must specify a test directory. -dir path/to/some/where");
268                 }
269                 if (cl.hasOption(OPTION_TESTREST)) {
270                         this.isREST = true;
271                 } else {
272                         this.isREST = false;
273                 }
274                 if (cl.hasOption(OPTION_TESTURL)) {
275                         this.restURL = new URL(cl.getOptionValue(OPTION_TESTURL));
276                 }
277                 if (cl.hasOption(OPTION_TESTOUTPUT)) {
278                         this.output = Paths.get(cl.getOptionValue(OPTION_TESTOUTPUT));
279                 } else {
280                         this.output = Paths.get(this.directory, "results");
281                 }
282                 if (cl.hasOption(OPTION_LOOP)) {
283                         this.loop = Integer.parseInt(cl.getOptionValue(OPTION_LOOP));
284                 }
285                 if (cl.hasOption(OPTION_TESTNUMBERS)) {
286                         String testNumberString = cl.getOptionValue(OPTION_TESTNUMBERS);
287                         testNumbersArray = testNumberString.split(",");
288                         //
289                         // reset strings to include dots so they exactly match pattern in file name
290                         //
291                         for (int i = 0; i < testNumbersArray.length; i++) {
292                                 testNumbersArray[i] = "." + testNumbersArray[i] + ".";
293                         }
294                 }
295         }
296         
297         /**
298          * Using the command line options that were parsed, configures our test instance.
299          * 
300          * @throws FactoryException
301          */
302         protected void configure() throws FactoryException {
303                 //
304                 // Setup the xacml.properties file
305                 //
306                 if (this.directory == null) {
307                         throw new IllegalArgumentException("Must supply a path to a test directory.");
308                 }
309                 Path pathDir = Paths.get(this.directory, "xacml.properties");
310                 if (Files.notExists(pathDir)) {
311                         throw new IllegalArgumentException(pathDir.toString() + " does not exist.");
312                 }
313                 //
314                 // Set it as the System variable so the XACML factories know where the properties are
315                 // loaded from.
316                 //
317                 System.setProperty(XACMLProperties.XACML_PROPERTIES_NAME, pathDir.toString());
318                 //
319                 // Now we can create the data type factory
320                 //
321                 dataTypeFactory = DataTypeFactory.newInstance();
322                 //
323                 // Load in what generators we are to create
324                 //
325                 String generators = XACMLProperties.getProperty(PROP_GENERATOR);
326                 if (generators != null) {
327                         //
328                         // Parse the generators
329                         //
330                         for (String generator : Splitter.on(',').trimResults().omitEmptyStrings().split(generators)) {
331                                 this.configureGenerator(generator);
332                         }
333                 }
334                 //
335                 // If we are embedded, create our engine
336                 //
337                 if (this.isREST == false) {
338                         PDPEngineFactory factory = PDPEngineFactory.newInstance();
339                         this.engine = factory.newEngine();
340                 }
341                 //
342                 // Remove all the responses from the results directory
343                 //
344                 this.removeResults();
345         }
346         
347         /**
348          * Removes all the Response* files from the results directory.
349          * 
350          */
351         public void     removeResults() {
352                 try {
353                         //
354                         // Determine where the results are supposed to be written to
355                         //
356                         Path resultsPath;
357                         if (this.output != null) {
358                                 resultsPath = this.output;
359                         } else {
360                                 resultsPath = Paths.get(this.directory.toString(), "results");
361                         }
362                         //
363                         // Walk the files
364                         //
365                         Files.walkFileTree(resultsPath, new SimpleFileVisitor<Path>() {
366
367                                 @Override
368                                 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
369                                         if (file.getFileName().toString().startsWith("Response")) {
370                                                 Files.delete(file);
371                                         }
372                                         return super.visitFile(file, attrs);
373                                 }                               
374                         });
375                 } catch (IOException e) {
376                         logger.error("Failed to removeRequests from " + this.directory + " " + e);
377                 }
378         }
379         
380         /**
381          * Configure's a specific generator instance from the properties file.
382          * 
383          * @param generator
384          */
385         protected void configureGenerator(String generator) {
386                 String prefix = PROP_GENERATOR + "." + generator;
387                 String file = XACMLProperties.getProperty(prefix + ".file");
388                 //
389                 // Create a generator object
390                 //
391                 Generator gen = new Generator(Paths.get(this.directory, file));
392                 this.generators.add(gen);
393                 //
394                 // Grab attributes
395                 //
396                 String attributes = XACMLProperties.getProperty(prefix + ".attributes");
397                 for (String attribute : Splitter.on(',').trimResults().omitEmptyStrings().split(attributes)) {
398                         String attributePrefix = prefix + ".attributes." + attribute;
399                         //
400                         // Create an attribute value. It is simply a placeholder for the field within
401                         // the CSV that contains the actual attribute value. It mainly holds the data type
402                         //
403                         Identifier datatype = new IdentifierImpl(XACMLProperties.getProperty(attributePrefix + ".datatype"));
404                         Integer field = Integer.parseInt(XACMLProperties.getProperty(attributePrefix + ".field"));
405                         StdAttributeValue<?> value = new StdAttributeValue<>(datatype, field);
406                         //
407                         // Get the rest of the attribute properties
408                         //
409                         Identifier category = new IdentifierImpl(XACMLProperties.getProperty(attributePrefix + ".category"));
410                         Identifier id = new IdentifierImpl(XACMLProperties.getProperty(attributePrefix + ".id"));
411                         String issuer = XACMLProperties.getProperty(attributePrefix + ".issuer");
412                         boolean include = Boolean.parseBoolean(XACMLProperties.getProperty(attributePrefix + ".include", "false"));
413                         //
414                         // Now we have a skeleton attribute
415                         //
416                         gen.attributes.add(new StdMutableAttribute(category, id, value, issuer, include));
417                 }
418         }
419         
420         /**
421          * This runs() the test instance. It first configure's itself and then walks the
422          * requests directory issue each request to the PDP engine.
423          * 
424          * @throws IOException
425          * @throws FactoryException 
426          * 
427          */
428         public void run() throws IOException, FactoryException {
429                 //
430                 // Configure ourselves
431                 //
432                 this.configure();
433                 //
434                 // Loop and run
435                 //
436                 int runs = 1;
437                 do {
438                         long lTimeStart = System.currentTimeMillis();
439                         logger.info("Run number: " + runs);
440                         //
441                         // Walk the request directory
442                         //
443                         Files.walkFileTree(Paths.get(this.directory.toString(), "requests"), this);
444                         long lTimeEnd = System.currentTimeMillis();
445                         logger.info("Run elapsed time: " + (lTimeEnd - lTimeStart) + "ms");
446                         //
447                         // Dump the stats
448                         //
449                         this.dumpStats();
450                         this.resetStats();
451                         //
452                         // Increment
453                         //
454                         runs++;
455                 } while ((this.loop == -1 ? true : runs <= this.loop));
456         }
457         
458         @Override
459         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
460                 //
461                 // Sanity check the file name
462                 //
463                 Matcher matcher = this.pattern.matcher(file.getFileName().toString());
464                 if (matcher.matches()) {
465                         //
466                         // if user has limited which files to use, check that here
467                         //
468                         if (testNumbersArray != null) {
469                                 String fileNameString = file.getFileName().toString();
470                                 boolean found = false;
471                                 for (String numberString : testNumbersArray) {
472                                         if (fileNameString.contains(numberString)) {
473                                                 found = true;
474                                                 break;
475                                         }
476                                 }
477                                 if (found == false) {
478                                         //
479                                         // this test is not in the list to be run, so skip it
480                                         //
481                                         return super.visitFile(file, attrs);
482                                 }
483                         }
484                         try {
485                                 //
486                                 // Pull what this request is supposed to be
487                                 //
488                                 String group = null;
489                                 int count = matcher.groupCount();
490                                 if (count >= 1) {
491                                         group = matcher.group(count-1);
492                                 }
493                                 //
494                                 // Send it
495                                 //
496                                 this.sendRequest(file, group);
497                         } catch (Exception e) {
498                                 logger.error(e);
499                                 e.printStackTrace();
500                         }
501                 }
502                 return super.visitFile(file, attrs);
503         }
504         
505         /**
506          * When a request file is encountered, this method is called send the request to the PDP engine. It will also dump
507          * the response object. If the group equals "Generate", then it will loop and send the request with generated attributes
508          * until that list is empty.
509          * 
510          * @param file - Request file. Eg. Request-01-Permit.json
511          * @param group - This is the parsed out string of the request file that defines if it is a Permit/Deny/Generate etc.
512          * @throws Exception
513          */
514         protected void sendRequest(Path file, String group) throws Exception {
515                 logger.info(file.toString());
516                 int requestCount = 0;
517                 do {
518                         //
519                         // Generate the request
520                         //
521                         Request request = this.generateRequest(file, group);
522                         //
523                         // Was something generated?
524                         //
525                         if (request == null) {
526                                 //
527                                 // Get out of the loop
528                                 //
529                                 logger.info("NULL request generated.");
530                                 break;
531                         }
532                         logger.info(request);
533                         //
534                         // Call the PDP
535                         //
536                         Response response = this.callPDP(request);
537                         //
538                         // Process the response
539                         //
540                         this.processResponse(file, request, response, group, requestCount);
541                         //
542                         // Is this a generated request?
543                         //
544                         if (group.equals("Generate")) {
545                                 //
546                                 // Yes, increment counter and move
547                                 // on to the next generated request.
548                                 //
549                                 requestCount++;
550                         } else {
551                                 //
552                                 // Nope, exit the loop
553                                 //
554                                 break;
555                         }
556                 } while (group.equals("Generate"));
557         }
558         
559         /**
560          * Sends the request object to the PDP engine. Either the embedded engine or the RESTful engine.
561          * 
562          * @param request - XACML request object
563          * @return Response - returns the XACML response object
564          */
565         protected Response callPDP(Request request) {
566                 //
567                 // Send it to the PDP
568                 //
569                 Response response = null;
570                 if (this.isREST) {
571                         try {
572                                 String jsonString = JSONRequest.toString(request, false);
573                                 //
574                                 // Call RESTful PDP
575                                 //
576                                 response = this.callRESTfulPDP(new ByteArrayInputStream(jsonString.getBytes()));
577                         } catch (Exception e) {
578                                 logger.error("Error in sending RESTful request: " + e, e);
579                         }
580                 } else {
581                         //
582                         // Embedded call to PDP
583                         //
584                         long lTimeStart = System.currentTimeMillis();
585                         try {
586                                 response = this.engine.decide(request);
587                         } catch (PDPException e) {
588                                 logger.error(e);
589                         }
590                         long lTimeEnd = System.currentTimeMillis();
591                         logger.info("Elapsed Time: " + (lTimeEnd - lTimeStart) + "ms");
592                 }
593                 return response;
594         }
595         
596         /**
597          * Reads the request file into a Request object based on its type.
598          * 
599          * If the request has "Generate" in its filename, then this function will add
600          * generated attributes into the request.
601          * 
602          * @param file - Request file. Eg. Request-01-Permit.json
603          * @param group - This is the parsed out string of the request file that defines if it is a Permit/Deny/Generate etc.
604          * @return
605          * @throws JSONStructureException
606          * @throws DOMStructureException
607          * @throws PEPException
608          */
609         protected Request generateRequest(Path file, String group) throws JSONStructureException, DOMStructureException, PEPException {
610                 //
611                 // Convert to a XACML Request Object
612                 //
613                 Request request = null;
614                 if (TestBase.isJSON(file)) {
615                         request = JSONRequest.load(file.toFile());
616                 } else if (TestBase.isXML(file)) {
617                         request = DOMRequest.load(file.toFile());
618                 }
619                 if (request == null) {
620                         throw new PEPException("Invalid Request File: " + file.toString());
621                 }
622                 //
623                 // Only if this request has "Generate"
624                 // Request.XX.Generate.[json|xml]
625                 //
626                 if (group.equals("Generate")) {
627                         //
628                         // Add attributes to it
629                         //
630                         request = this.onNextRequest(request);
631                 }
632                 //
633                 // Done
634                 //
635                 return request;
636         }
637
638         /**
639          * Called to add in generated attributes into the request.
640          * 
641          * @param request
642          * @return
643          */
644         protected Request onNextRequest(Request request) {
645                 //
646                 // If we have no generators, just return
647                 //
648                 if (this.generators.isEmpty()) {
649                         return request;
650                 }
651                 //
652                 // Copy the request attributes
653                 //
654                 List<StdMutableRequestAttributes> attributes = new ArrayList<StdMutableRequestAttributes>();
655                 for (RequestAttributes a : request.getRequestAttributes()) {
656                         attributes.add(new StdMutableRequestAttributes(a));
657                 }
658                 //
659                 // Iterate the generators
660                 //
661                 for (Generator generator : this.generators) {
662                         //
663                         // Read a row in
664                         //
665                         String line = generator.read();
666                         //
667                         // Was something read?
668                         //
669                         if (line == null) {
670                                 //
671                                 // No more rows to read, return null
672                                 //
673                                 return null;
674                         }
675                         //
676                         // Split the line
677                         //
678                         List<String> fields = Lists.newArrayList(Splitter.on(',').trimResults().split(line));
679                         //
680                         // Now work on the attributes
681                         //
682                         for (StdMutableAttribute attribute : generator.attributes) {
683                                 //
684                                 // Grab the attribute holder, which holds the datatype and field. There should
685                                 // be only ONE object in the collection.
686                                 //
687                                 AttributeValue<?> value = attribute.getValues().iterator().next();
688                                 Integer field = (Integer) value.getValue();
689                                 //
690                                 // Is the field number valid?
691                                 //
692                                 if (field >= fields.size()) {
693                                         logger.error("Not enough fields: " + field + "(" + fields.size() + ")");
694                                         return null;
695                                 }
696                                 //
697                                 // Determine what datatype it is
698                                 //
699                                 DataType<?> dataTypeExtended    = dataTypeFactory.getDataType(value.getDataTypeId());
700                                 if (dataTypeExtended == null) {
701                                         logger.error("Failed to determine datatype");
702                                         return null;
703                                 }
704                                 //
705                                 // Create the attribute value
706                                 //
707                                 try {
708                                         AttributeValue<?> attributeValue = dataTypeExtended.createAttributeValue(fields.get(field));                                    
709                                         //
710                                         // Create the attribute
711                                         //
712                                         StdMutableAttribute newAttribute = new StdMutableAttribute(attribute.getCategory(),
713                                                                                                                                                                 attribute.getAttributeId(),
714                                                                                                                                                                 attributeValue,
715                                                                                                                                                                 attribute.getIssuer(),
716                                                                                                                                                                 attribute.getIncludeInResults());
717                                         boolean added = false;
718                                         for (StdMutableRequestAttributes a : attributes) {
719                                                 //
720                                                 // Does the category exist?
721                                                 //
722                                                 if (a.getCategory().equals(attribute.getCategory())) {
723                                                         //
724                                                         // Yes - add in the new attribute value
725                                                         //
726                                                         a.add(newAttribute);
727                                                         added = true;
728                                                         break;
729                                                 }
730                                         }
731                                         if (added == false) {
732                                                 //
733                                                 // New category - create it and add it in
734                                                 //
735                                                 StdMutableRequestAttributes a = new StdMutableRequestAttributes(); 
736                                                 a.setCategory(newAttribute.getCategory());
737                                                 a.add(newAttribute);
738                                                 attributes.add(a);
739                                         }
740                                 } catch (DataTypeException e) {
741                                         logger.error(e);
742                                         return null;
743                                 }
744                         }
745                 }
746                 //
747                 // Now form our final request
748                 //
749                 StdMutableRequest newRequest = new StdMutableRequest();
750                 newRequest.setCombinedDecision(request.getCombinedDecision());
751                 newRequest.setRequestDefaults(request.getRequestDefaults());
752                 newRequest.setReturnPolicyIdList(request.getReturnPolicyIdList());
753                 newRequest.setStatus(request.getStatus());
754                 for (StdMutableRequestAttributes a : attributes) {
755                         newRequest.add(a);
756                 }
757                 return newRequest;
758         }
759
760         /**
761          * This makes an HTTP POST call to a running PDP RESTful servlet to get a decision.
762          * 
763          * @param file
764          * @return
765          */
766         protected Response callRESTfulPDP(InputStream is) {
767                 Response response = null;
768                 HttpURLConnection connection = null;
769                 try {
770
771                         //
772                         // Open up the connection
773                         //
774                         connection = (HttpURLConnection) this.restURL.openConnection();
775                         connection.setRequestProperty("Content-Type", "application/json");
776                         //
777                         // Setup our method and headers
778                         //
779             connection.setRequestMethod("POST");
780             connection.setUseCaches(false);
781             //
782             // Adding this in. It seems the HttpUrlConnection class does NOT
783             // properly forward our headers for POST re-direction. It does so
784             // for a GET re-direction.
785             //
786             // So we need to handle this ourselves.
787             //
788             connection.setInstanceFollowRedirects(false);
789                         connection.setDoOutput(true);
790                         connection.setDoInput(true);
791                         //
792                         // Send the request
793                         //
794                         try (OutputStream os = connection.getOutputStream()) {
795                                 IOUtils.copy(is, os);
796                         }
797             //
798             // Do the connect
799             //
800             connection.connect();
801             if (connection.getResponseCode() == 200) {
802                 //
803                 // Read the response
804                 //
805                         ContentType contentType = null;
806                         try {
807                                 contentType = ContentType.parse(connection.getContentType());
808                                 
809                                 if (contentType.getMimeType().equalsIgnoreCase(ContentType.APPLICATION_JSON.getMimeType())) {
810                                 response = JSONResponse.load(connection.getInputStream());
811                                 } else if (contentType.getMimeType().equalsIgnoreCase(ContentType.APPLICATION_XML.getMimeType()) ||
812                                                 contentType.getMimeType().equalsIgnoreCase("application/xacml+xml") ) {
813                                 response = DOMResponse.load(connection.getInputStream());
814                                 } else {
815                                 logger.error("unknown content-type: " + contentType);
816                         }
817
818                 } catch (Exception e) {
819                                 String message = "Parsing Content-Type: " + connection.getContentType() + ", error=" + e.getMessage();
820                                 logger.error(message, e);
821                         }
822
823             } else {
824                 logger.error(connection.getResponseCode() + " " + connection.getResponseMessage());
825             }
826                 } catch (Exception e) {
827                         logger.error(e);
828                 }
829                 
830                 return response;
831         }
832         
833         /**
834          * This processes a response. Saves the response out to disk. If there is a corresponding response file for the request located
835          * in the "responses" sub-directory, then this method will compare that response file with what the engine returned to see if it
836          * matched.
837          * 
838          * @param requestFile
839          * @param request
840          * @param response
841          * @param group
842          * @param count
843          * @throws Exception
844          */
845         protected void processResponse(Path requestFile, Request request, Response response, String group, int count) throws Exception {
846                 //
847                 // Construct the output filename
848                 //
849                 Path responseFile = null;
850                 Path resultFile = null;
851                 int num = requestFile.getNameCount();
852                 if (num < 2) {
853                         logger.error("Too few dir's in request filename.");
854                         throw new Exception("Too few dir's in request filename. Format should be Request.[0-9]+.{Permit|Deny|NA|Indeterminate}.{json|xml}");
855                 }
856                 String filename = requestFile.getFileName().toString();
857                 if (group.equals("Generate")) {
858                         //
859                         // Using count variable, construct a filename
860                         //
861                         //              i.e. Response.03.Generate.{count}.json
862                         //
863                         filename = "Response" + filename.substring(filename.indexOf('.'), filename.lastIndexOf('.')) + String.format("%03d", count) + filename.substring(filename.lastIndexOf('.'));
864                 } else {
865                         //
866                         // Construct filename
867                         //
868                         filename = "Response" + filename.substring(filename.indexOf('.'));
869                 }
870                 //
871                 // Determine equivalent response file path
872                 //
873                 responseFile = Paths.get(requestFile.subpath(0, num - 2).toString(), "responses");
874                 if (Files.notExists(responseFile)) {
875                         //
876                         // Create it
877                         //
878                         logger.warn(responseFile.toString() + " does NOT exist, creating...");
879                         try {
880                                 Files.createDirectories(responseFile);
881                         } catch (IOException e) {
882                                 logger.error(e);
883                                 throw new Exception("Cannot proceed without an output directory.");
884                         }
885                 }
886                 responseFile = Paths.get(responseFile.toString(), filename);
887                 //
888                 // Determine path to write result file
889                 //
890                 if (this.output != null) {
891                         //
892                         // User specified an output path
893                         //
894                         resultFile = this.output;
895                 } else {
896                         //
897                         // Default path
898                         //
899                         resultFile = Paths.get(requestFile.subpath(0, num - 2).toString(), "results");
900                 }
901                 //
902                 // Check if the path exists
903                 //
904                 if (Files.notExists(resultFile)) {
905                         //
906                         // Create it
907                         //
908                         logger.warn(resultFile.toString() + " does NOT exist, creating...");
909                         try {
910                                 Files.createDirectories(resultFile);
911                         } catch (IOException e) {
912                                 logger.error(e);
913                                 throw new Exception("Cannot proceed without an output directory.");
914                         }
915                 }
916                 //
917                 // Add the filename to the path
918                 //
919                 resultFile = Paths.get(resultFile.toString(), filename);
920                 //
921                 // Check if there is an equivalent response in the response
922                 // directory. If so, compare our response result with that one.
923                 //
924                 boolean succeeded = true;
925                 if (responseFile != null && Files.exists(responseFile)) {
926                         //
927                         // Do comparison
928                         //
929                         Response expectedResponse = null;
930                         if (TestBase.isJSON(responseFile)) {
931                                 expectedResponse = JSONResponse.load(responseFile);
932                         } else if (TestBase.isXML(responseFile)) {
933                                 expectedResponse = DOMResponse.load(responseFile);
934                         }
935                         if (expectedResponse != null) {
936                                 //
937                                 // Do the compare
938                                 //
939                                 if (response == null) {
940                                         logger.error("NULL response returned.");
941                                         this.responseNotMatches++;
942                                         succeeded = false;
943                                 } else {
944                                         if (response.equals(expectedResponse)) {
945                                                 logger.info("Response matches expected response.");
946                                                 this.responseMatches++;
947                                         } else {
948                                                 logger.error("Response does not match expected response.");
949                                                 logger.error("Expected: ");
950                                                 logger.error(expectedResponse.toString());
951                                                 this.responseNotMatches++;
952                                                 succeeded = false;
953                                         }
954                                 }
955                         }
956                 }
957                 //
958                 // Write the response to the result file
959                 //
960                 logger.info("Request: " + requestFile.getFileName() + " response is: " + (response == null ? "null" : response.toString()));
961                 if (resultFile != null && response != null) {
962                         if (TestBase.isJSON(resultFile)) {
963                                 Files.write(resultFile, JSONResponse.toString(response, true).getBytes());
964                         } else if (TestBase.isXML(resultFile)) {
965                                 Files.write(resultFile, DOMResponse.toString(response, true).getBytes());
966                         }
967                 }
968                 //
969                 // Stats
970                 //              
971                 if (group.equals("Permit")) {
972                         this.expectedPermits++;
973                 } else if (group.equals("Deny")) {
974                         this.expectedDenies++;
975                 } else if (group.equals("NA")) {
976                         this.expectedNotApplicables++;
977                 } else if (group.equals("Indeterminate")) {
978                         this.expectedIndeterminates++;
979                 }
980                 if (response != null) {
981                         for (Result result : response.getResults()) {
982                                 Decision decision = result.getDecision();
983                                 if (group.equals("Generate")) {
984                                         if (decision.equals(Decision.PERMIT)) {
985                                                 this.generatedpermits++;
986                                         } else if (decision.equals(Decision.DENY)) {
987                                                 this.generateddenies++;
988                                         } else if (decision.equals(Decision.NOTAPPLICABLE)) {
989                                                 this.generatednotapplicables++;
990                                         } else if (decision.equals(Decision.INDETERMINATE)) {
991                                                 this.generatedindeterminates++;
992                                         }
993                                         continue;
994                                 }
995                                 if (decision.equals(Decision.PERMIT)) {
996                                         this.permits++;
997                                         if (group.equals("Permit") == false) {
998                                                 succeeded = false;
999                                                 logger.error("Expected " + group + " got " + decision);
1000                                         }
1001                                 } else if (decision.equals(Decision.DENY)) {
1002                                         this.denies++;
1003                                         if (group.equals("Deny") == false) {
1004                                                 succeeded = false;
1005                                                 logger.error("Expected " + group + " got " + decision);
1006                                         }
1007                                 } else if (decision.equals(Decision.NOTAPPLICABLE)) {
1008                                         this.notapplicables++;
1009                                         if (group.equals("NA") == false) {
1010                                                 succeeded = false;
1011                                                 logger.error("Expected " + group + " got " + decision);
1012                                         }
1013                                 } else if (decision.equals(Decision.INDETERMINATE)) {
1014                                         this.indeterminates++;
1015                                         if (group.equals("Indeterminate") == false) {
1016                                                 succeeded = false;
1017                                                 logger.error("Expected " + group + " got " + decision);
1018                                         }
1019                                 }
1020                         }
1021                 }
1022                 if (succeeded) {
1023                         logger.info("REQUEST SUCCEEDED");
1024                 } else {
1025                         logger.info("REQUEST FAILED");
1026                 }
1027         }
1028
1029         protected void  dumpStats() {
1030                 StringBuilder dump = new StringBuilder();
1031                 dump.append(System.lineSeparator());
1032                 dump.append("Permits: " + this.permits + " Expected: " + this.expectedPermits);
1033                 dump.append(System.lineSeparator());
1034                 dump.append("Denies: " + this.denies + " Expected: " + this.expectedDenies);
1035                 dump.append(System.lineSeparator());
1036                 dump.append("NA: " + this.notapplicables + " Expected: " + this.expectedNotApplicables);
1037                 dump.append(System.lineSeparator());
1038                 dump.append("Indeterminates: " + this.indeterminates + " Expected: " + this.expectedIndeterminates);
1039                 dump.append(System.lineSeparator());
1040                 dump.append("Generated Permits: " + this.generatedpermits);
1041                 dump.append(System.lineSeparator());
1042                 dump.append("Generated Denies: " + this.generateddenies);
1043                 dump.append(System.lineSeparator());
1044                 dump.append("Generated NA: " + this.generatednotapplicables);
1045                 dump.append(System.lineSeparator());
1046                 dump.append("Generated Indeterminates: " + this.generatedindeterminates);
1047                 dump.append(System.lineSeparator());
1048                 dump.append("Responses Matched: " + this.responseMatches);
1049                 dump.append(System.lineSeparator());
1050                 dump.append("Responses NOT Matched: " + this.responseNotMatches);
1051                 
1052                 if (this.permits != this.expectedPermits ||
1053                         this.denies != this.expectedDenies ||
1054                         this.notapplicables != this.expectedNotApplicables ||
1055                         this.indeterminates != this.expectedIndeterminates ||
1056                         this.responseNotMatches > 0) {
1057                         logger.error(dump.toString());
1058                 } else {
1059                         logger.info(dump.toString());
1060                 }
1061         }
1062         
1063         protected void  resetStats() {
1064                 this.permits = 0;
1065                 this.denies = 0;
1066                 this.notapplicables = 0;
1067                 this.indeterminates = 0;
1068                 this.generatedpermits = 0;
1069                 this.generateddenies = 0;
1070                 this.generatednotapplicables = 0;
1071                 this.generatedindeterminates = 0;
1072                 this.responseMatches = 0;
1073                 this.responseNotMatches = 0;
1074         }
1075
1076         public static void main(String[] args) {
1077                 try {
1078                         new TestBase(args).run();
1079                 } catch (ParseException | IOException | FactoryException e) {
1080                         logger.error(e);
1081                 } catch (HelpException e) {
1082                 }               
1083         }
1084 }