]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
ccde889b374717045d65c69adedabc18005e5a10
[tmp/jakarta-migration.git] /
1 package org.collectionspace.services.IntegrationTests.xmlreplay;\r
2 \r
3 import org.apache.commons.cli.*;\r
4 \r
5 import org.apache.commons.io.FileUtils;\r
6 import org.apache.commons.jexl2.JexlEngine;\r
7 import org.collectionspace.services.common.api.FileTools;\r
8 import org.collectionspace.services.common.api.Tools;\r
9 import org.collectionspace.services.common.service.ServiceBindingType;\r
10 import org.dom4j.*;\r
11 import org.dom4j.io.SAXReader;\r
12 \r
13 import java.io.*;\r
14 import java.util.*;\r
15 \r
16 /**  This class is used to replay a request to the Services layer, by sending the XML payload\r
17  *   in an appropriate Multipart request.\r
18  *   See example usage in calling class XmlReplayTest in services/IntegrationTests, and also in main() in this class.\r
19  *   @author Laramie Crocker\r
20  */\r
21 public class XmlReplay {\r
22 \r
23     public XmlReplay(String basedir, String reportsDir){\r
24         this.basedir = basedir;\r
25         this.serviceResultsMap = createResultsMap();\r
26         this.reportsList = new ArrayList<String>();\r
27         this.reportsDir = reportsDir;\r
28     }\r
29 \r
30     public static final String DEFAULT_CONTROL = "xml-replay-control.xml";\r
31     public static final String DEFAULT_MASTER_CONTROL = "xml-replay-master.xml";\r
32     public static final String DEFAULT_DEV_MASTER_CONTROL = "dev-master.xml";\r
33 \r
34     private String reportsDir = "";\r
35     public String getReportsDir(){\r
36         return reportsDir;\r
37     }\r
38     private String basedir = ".";  //set from constructor.\r
39     public String getBaseDir(){\r
40         return basedir;\r
41     }\r
42     \r
43     private String controlFileName = DEFAULT_CONTROL;\r
44     public String getControlFileName() {\r
45         return controlFileName;\r
46     }\r
47     public void setControlFileName(String controlFileName) {\r
48         this.controlFileName = controlFileName;\r
49     }\r
50 \r
51     private String protoHostPort = "";\r
52     public String getProtoHostPort() {\r
53         return protoHostPort;\r
54     }\r
55     public void setProtoHostPort(String protoHostPort) {\r
56         this.protoHostPort = protoHostPort;\r
57     }\r
58 \r
59     private boolean autoDeletePOSTS = true;\r
60     public boolean isAutoDeletePOSTS() {\r
61         return autoDeletePOSTS;\r
62     }\r
63     public void setAutoDeletePOSTS(boolean autoDeletePOSTS) {\r
64         this.autoDeletePOSTS = autoDeletePOSTS;\r
65     }\r
66 \r
67     private Dump dump;\r
68     public Dump getDump() {\r
69         return dump;\r
70     }\r
71     public void setDump(Dump dump) {\r
72         this.dump = dump;\r
73     }\r
74 \r
75     AuthsMap defaultAuthsMap;\r
76     public AuthsMap getDefaultAuthsMap(){\r
77         return defaultAuthsMap;\r
78     }\r
79     public void setDefaultAuthsMap(AuthsMap authsMap){\r
80         defaultAuthsMap = authsMap;\r
81     }\r
82 \r
83     private Map<String, ServiceResult> serviceResultsMap;\r
84     public Map<String, ServiceResult> getServiceResultsMap(){\r
85         return serviceResultsMap;\r
86     }\r
87     public static Map<String, ServiceResult> createResultsMap(){\r
88         return new HashMap<String, ServiceResult>();\r
89     }\r
90 \r
91     private List<String> reportsList;\r
92     public  List<String> getReportsList(){\r
93         return reportsList;\r
94     }\r
95 \r
96     public String toString(){\r
97         return "XmlReplay{"+this.basedir+", "+this.defaultAuthsMap+", "+this.dump+", "+this.reportsDir+'}';\r
98     }\r
99 \r
100     // ============== METHODS ===========================================================\r
101 \r
102     /** Optional information method: call this method after instantiating this class using the constructor XmlReplay(String), which sets the basedir.  Then you\r
103      *   pass in your relative masterFilename to that basedir to this method, which will return true if the file is readable, valid xml, etc.\r
104      *   Do this in preference to  just seeing if File.exists(), because there are rules to finding the file relative to the maven test dir, yada, yada.\r
105      *   This method makes it easy to have a development test file that you don't check in, so that dev tests can be missing gracefully, etc.\r
106      */\r
107     public boolean masterConfigFileExists(String masterFilename){\r
108         try {\r
109             org.dom4j.Document doc = openMasterConfigFile(masterFilename);\r
110             if (doc == null){\r
111                 return false;\r
112             }\r
113             return true;\r
114         } catch (Throwable t){\r
115             return false;\r
116         }\r
117     }\r
118 \r
119     public org.dom4j.Document openMasterConfigFile(String masterFilename) throws FileNotFoundException {\r
120         String fullPath = Tools.glue(basedir, "/", masterFilename);\r
121         File f = new File(fullPath);\r
122         if (!f.exists()){\r
123             return null;\r
124         }\r
125         org.dom4j.Document document = getDocument(fullPath); //will check full path first, then checks relative to PWD.\r
126         if (document == null){\r
127             throw new FileNotFoundException("XmlReplay master control file ("+masterFilename+") not found in basedir: "+basedir+". Exiting test.");\r
128         }\r
129         return document;\r
130     }\r
131 \r
132     /** specify the master config file, relative to getBaseDir(), but ignore any tests or testGroups in the master.\r
133      *  @return a Document object, which you don't need to use: all options will be stored in XmlReplay instance.\r
134      */\r
135     public org.dom4j.Document readOptionsFromMasterConfigFile(String masterFilename) throws FileNotFoundException {\r
136         org.dom4j.Document document = openMasterConfigFile(masterFilename);\r
137         if (document == null){\r
138             throw new FileNotFoundException(masterFilename);\r
139         }\r
140         protoHostPort = document.selectSingleNode("/xmlReplayMaster/protoHostPort").getText().trim();\r
141         AuthsMap authsMap = readAuths(document);\r
142         setDefaultAuthsMap(authsMap);\r
143         Dump dump = XmlReplay.readDumpOptions(document);\r
144         setDump(dump);\r
145         return document;\r
146     }\r
147 \r
148     public List<List<ServiceResult>> runMaster(String masterFilename) throws Exception {\r
149         return runMaster(masterFilename, true);\r
150     }\r
151 \r
152     /** Creates new instances of XmlReplay, one for each controlFile specified in the master,\r
153      *  and setting defaults from this instance, but not sharing ServiceResult objects or maps. */\r
154     public List<List<ServiceResult>> runMaster(String masterFilename, boolean readOptionsFromMaster) throws Exception {\r
155         List<List<ServiceResult>> list = new ArrayList<List<ServiceResult>>();\r
156         org.dom4j.Document document;\r
157         if (readOptionsFromMaster){\r
158             document = readOptionsFromMasterConfigFile(masterFilename);\r
159         } else {\r
160             document = openMasterConfigFile(masterFilename);\r
161         }\r
162         if (document==null){\r
163             throw new FileNotFoundException(masterFilename);\r
164         }\r
165         String controlFile, testGroup, test;\r
166         List<Node> runNodes;\r
167         runNodes = document.selectNodes("/xmlReplayMaster/run");\r
168         for (Node runNode : runNodes) {\r
169             controlFile = runNode.valueOf("@controlFile");\r
170             testGroup = runNode.valueOf("@testGroup");\r
171             test = runNode.valueOf("@test"); //may be empty\r
172 \r
173             //Create a new instance and clone only config values, not any results maps.\r
174             XmlReplay replay = new XmlReplay(basedir, this.reportsDir);\r
175             replay.setControlFileName(controlFile);\r
176             replay.setProtoHostPort(protoHostPort);\r
177             replay.setAutoDeletePOSTS(isAutoDeletePOSTS());\r
178             replay.setDump(dump);\r
179             replay.setDefaultAuthsMap(getDefaultAuthsMap());\r
180 \r
181             //Now run *that* instance.\r
182             List<ServiceResult> results = replay.runTests(testGroup, test);\r
183             list.add(results);\r
184             this.reportsList.addAll(replay.getReportsList());   //Add all the reports from the inner replay, to our master replay's reportsList, to generate the index.html file.\r
185         }\r
186         XmlReplayReport.saveIndexForMaster(basedir, reportsDir, masterFilename, this.reportsList);\r
187         return list;\r
188     }\r
189 \r
190     /** Use this if you wish to run named tests within a testGroup, otherwise call runTestGroup(). */\r
191     public List<ServiceResult>  runTests(String testGroupID, String testID) throws Exception {\r
192         List<ServiceResult> result = runXmlReplayFile(this.basedir,\r
193                                 this.controlFileName,\r
194                                 testGroupID,\r
195                                 testID,\r
196                                 this.serviceResultsMap,\r
197                                 this.autoDeletePOSTS,\r
198                                 dump,\r
199                                 this.protoHostPort,\r
200                                 this.defaultAuthsMap,\r
201                                 this.reportsList,\r
202                                 this.reportsDir);\r
203         return result;\r
204     }\r
205 \r
206     /** Use this if you wish to specify just ONE test to run within a testGroup, otherwise call runTestGroup(). */\r
207     public ServiceResult  runTest(String testGroupID, String testID) throws Exception {\r
208         List<ServiceResult> result = runXmlReplayFile(this.basedir,\r
209                                 this.controlFileName,\r
210                                 testGroupID,\r
211                                 testID,\r
212                                 this.serviceResultsMap,\r
213                                 this.autoDeletePOSTS,\r
214                                 dump,\r
215                                 this.protoHostPort,\r
216                                 this.defaultAuthsMap,\r
217                                 this.reportsList,\r
218                                 this.reportsDir);\r
219         if (result.size()>1){\r
220             throw new IndexOutOfBoundsException("Multiple ("+result.size()+") tests with ID='"+testID+"' were found within test group '"+testGroupID+"', but there should only be one test per ID attribute.");\r
221         }\r
222         return result.get(0);\r
223     }\r
224 \r
225     /** Use this if you wish to run all tests within a testGroup.*/\r
226     public List<ServiceResult> runTestGroup(String testGroupID) throws Exception {\r
227         //NOTE: calling runTest with empty testID runs all tests in a test group, but don't expose this fact.\r
228         // Expose this method (runTestGroup) instead.\r
229         return runTests(testGroupID, "");\r
230     }\r
231 \r
232     public List<ServiceResult>  autoDelete(String logName){\r
233         return autoDelete(this.serviceResultsMap, logName);\r
234     }\r
235 \r
236     /** Use this method to clean up resources created on the server that returned CSIDs, if you have\r
237      *  specified autoDeletePOSTS==false, which means you are managing the cleanup yourself.\r
238      * @param serviceResultsMap a Map of ServiceResult objects, which will contain ServiceResult.deleteURL.\r
239      * @return a List<String> of debug info about which URLs could not be deleted.\r
240      */\r
241     public static List<ServiceResult> autoDelete(Map<String, ServiceResult> serviceResultsMap, String logName){\r
242         List<ServiceResult> results = new ArrayList<ServiceResult>();\r
243         for (ServiceResult pr : serviceResultsMap.values()){\r
244             try {\r
245                 if (Tools.notEmpty(pr.deleteURL)){\r
246                     ServiceResult deleteResult = XmlReplayTransport.doDELETE(pr.deleteURL, pr.auth, pr.testID, "[autodelete:"+logName+"]");\r
247                     results.add(deleteResult);\r
248                 } else {\r
249                     ServiceResult errorResult = new ServiceResult();\r
250                     errorResult.fullURL = pr.fullURL;\r
251                     errorResult.testGroupID = pr.testGroupID;\r
252                     errorResult.fromTestID = pr.fromTestID;\r
253                     errorResult.overrideGotExpectedResult();\r
254                     results.add(errorResult);\r
255                 }\r
256             } catch (Throwable t){\r
257                 String s = (pr!=null) ? "ERROR while cleaning up ServiceResult map: "+pr+" for "+pr.deleteURL+" :: "+t\r
258                                       : "ERROR while cleaning up ServiceResult map (null ServiceResult): "+t;\r
259                 System.err.println(s);\r
260                 ServiceResult errorResult = new ServiceResult();\r
261                 errorResult.fullURL = pr.fullURL;\r
262                 errorResult.testGroupID = pr.testGroupID;\r
263                 errorResult.fromTestID = pr.fromTestID;\r
264                 errorResult.error = s;\r
265                 results.add(errorResult);\r
266             }\r
267         }\r
268         return results;\r
269     }\r
270 \r
271     public static class AuthsMap {\r
272         Map<String,String> map;\r
273         String defaultID="";\r
274         public String getDefaultAuth(){\r
275             return map.get(defaultID);\r
276         }\r
277         public String toString(){\r
278             return "AuthsMap: {default='"+defaultID+"'; "+map.keySet()+'}';\r
279         }\r
280     }\r
281 \r
282     public static AuthsMap readAuths(org.dom4j.Document document){\r
283     Map<String, String> map = new HashMap<String, String>();\r
284         List<Node> authNodes = document.selectNodes("//auths/auth");\r
285         for (Node auth : authNodes) {\r
286             map.put(auth.valueOf("@ID"), auth.getStringValue());\r
287         }\r
288         AuthsMap authsMap = new AuthsMap();\r
289         Node auths = document.selectSingleNode("//auths");\r
290         String defaultID = "";\r
291         if (auths != null){\r
292             defaultID = auths.valueOf("@default");\r
293         }\r
294         authsMap.map = map;\r
295         authsMap.defaultID = defaultID;\r
296         return authsMap;\r
297     }\r
298 \r
299     public static class Dump {\r
300         public boolean payloads = false;\r
301         //public static final ServiceResult.DUMP_OPTIONS dumpServiceResultOptions = ServiceResult.DUMP_OPTIONS;\r
302         public ServiceResult.DUMP_OPTIONS dumpServiceResult = ServiceResult.DUMP_OPTIONS.minimal;\r
303         public String toString(){\r
304             return "payloads: "+payloads+" dumpServiceResult: "+dumpServiceResult;\r
305         }\r
306     }\r
307 \r
308     public static Dump getDumpConfig(){\r
309         return new Dump();\r
310     }\r
311 \r
312     public static Dump readDumpOptions(org.dom4j.Document document){\r
313         Dump dump = getDumpConfig();\r
314         Node dumpNode = document.selectSingleNode("//dump");\r
315         if (dumpNode != null){\r
316             dump.payloads = Tools.isTrue(dumpNode.valueOf("@payloads"));\r
317             String dumpServiceResultStr = dumpNode.valueOf("@dumpServiceResult");\r
318             if (Tools.notEmpty(dumpServiceResultStr)){\r
319                 dump.dumpServiceResult = ServiceResult.DUMP_OPTIONS.valueOf(dumpServiceResultStr);\r
320             }\r
321         }\r
322         return dump;\r
323     }\r
324 \r
325     private static class PartsStruct {\r
326         public List<Map<String,String>> varsList = new ArrayList<Map<String,String>>();\r
327         String responseFilename = "";\r
328         String overrideTestID = "";\r
329         String startElement = "";\r
330         String label = "";\r
331 \r
332         public static PartsStruct readParts(Node testNode, final String testID, String xmlReplayBaseDir){\r
333             PartsStruct resultPartsStruct = new PartsStruct();\r
334             resultPartsStruct.responseFilename = testNode.valueOf("filename");\r
335             resultPartsStruct.startElement = testNode.valueOf("startElement");\r
336             resultPartsStruct.label = testNode.valueOf("label");\r
337             String responseFilename = testNode.valueOf("filename");\r
338             if (Tools.notEmpty(responseFilename)){\r
339                 resultPartsStruct.responseFilename = xmlReplayBaseDir + '/' + responseFilename;\r
340                 List<Node> varNodes = testNode.selectNodes("vars/var");\r
341                 readVars(testNode, varNodes, resultPartsStruct);\r
342             }\r
343             return resultPartsStruct;\r
344         }\r
345 \r
346         private static void readVars(Node testNode, List<Node> varNodes, PartsStruct resultPartsStruct){\r
347             Map<String,String> vars = new HashMap<String,String>();\r
348             resultPartsStruct.varsList.add(vars);\r
349             //System.out.println("### vars: "+vars.size()+" ########");\r
350             for (Node var: varNodes){\r
351                 String ID = var.valueOf("@ID");\r
352                 String value = var.getText();\r
353                 //System.out.println("ID: "+ID+" value: "+value);\r
354                 vars.put(ID, value); //vars is already part of resultPartsStruct.varsList\r
355             }\r
356             //System.out.println("### end-vars ########");\r
357         }\r
358     }\r
359 \r
360 \r
361 \r
362     private static String fixupFullURL(String fullURL, String protoHostPort, String uri){\r
363         if ( ! uri.startsWith(protoHostPort)){\r
364             fullURL = Tools.glue(protoHostPort, "/", uri);\r
365         } else {\r
366             fullURL = uri;\r
367         }\r
368         return fullURL;\r
369     }\r
370 \r
371     private static String fromTestID(String fullURL, Node testNode, Map<String, ServiceResult> serviceResultsMap){\r
372         String fromTestID = testNode.valueOf("fromTestID");\r
373         if (Tools.notEmpty(fromTestID)){\r
374             ServiceResult getPR = serviceResultsMap.get(fromTestID);\r
375             if (getPR != null){\r
376                 fullURL = Tools.glue(fullURL, "/", getPR.location);\r
377             }\r
378         }\r
379         return fullURL;\r
380     }\r
381 \r
382     private static String CSIDfromTestID(Node testNode, Map<String, ServiceResult> serviceResultsMap){\r
383         String result = "";\r
384         String fromTestID = testNode.valueOf("fromTestID");\r
385         if (Tools.notEmpty(fromTestID)){\r
386             ServiceResult getPR = serviceResultsMap.get(fromTestID);\r
387             if (getPR != null){\r
388                 result = getPR.location;\r
389             }\r
390         }\r
391         return result;\r
392     }\r
393 \r
394     public static org.dom4j.Document getDocument(String xmlFileName) {\r
395         org.dom4j.Document document = null;\r
396         SAXReader reader = new SAXReader();\r
397         try {\r
398             document = reader.read(xmlFileName);\r
399         } catch (DocumentException e) {\r
400             System.out.println("ERROR reading document: "+e);\r
401             //e.printStackTrace();\r
402         }\r
403         return document;\r
404     }\r
405 \r
406     protected static String validateResponseSinglePayload(ServiceResult serviceResult,\r
407                                                  Map<String, ServiceResult> serviceResultsMap,\r
408                                                  PartsStruct expectedResponseParts,\r
409                                                  XmlReplayEval evalStruct)\r
410     throws Exception {\r
411         String OK = "";\r
412         byte[] b = FileUtils.readFileToByteArray(new File(expectedResponseParts.responseFilename));\r
413         String expectedPartContent = new String(b);\r
414         Map<String,String> vars = expectedResponseParts.varsList.get(0);  //just one part, so just one varsList.  Must be there, even if empty.\r
415         expectedPartContent = evalStruct.eval(expectedPartContent, serviceResultsMap, vars, evalStruct.jexl, evalStruct.jc);\r
416         serviceResult.expectedContentExpanded = expectedPartContent;\r
417         String label = "NOLABEL";\r
418         String leftID  = "{from expected part, label:"+label+" filename: "+expectedResponseParts.responseFilename+"}";\r
419         String rightID = "{from server, label:"+label\r
420                             +" fromTestID: "+serviceResult.fromTestID\r
421                             +" URL: "+serviceResult.fullURL\r
422                             +"}";\r
423         String startElement = expectedResponseParts.startElement;\r
424         String partLabel = expectedResponseParts.label;\r
425         if (Tools.isBlank(startElement)){\r
426             if (Tools.notBlank(partLabel))\r
427             startElement = "/document/*[local-name()='"+partLabel+"']";\r
428         }\r
429         TreeWalkResults.MatchSpec matchSpec = TreeWalkResults.MatchSpec.createDefault();\r
430         TreeWalkResults list =\r
431             XmlCompareJdom.compareParts(expectedPartContent,\r
432                                         leftID,\r
433                                         serviceResult.result,\r
434                                         rightID,\r
435                                         startElement,\r
436                                         matchSpec);\r
437         serviceResult.addPartSummary(label, list);\r
438         return OK;\r
439     }\r
440 \r
441     protected static String validateResponse(ServiceResult serviceResult,\r
442                                              Map<String, ServiceResult> serviceResultsMap,\r
443                                              PartsStruct expectedResponseParts,\r
444                                              XmlReplayEval evalStruct){\r
445         String OK = "";\r
446         if (expectedResponseParts == null) return OK;\r
447         if (serviceResult == null) return OK;\r
448         if (serviceResult.result.length() == 0) return OK;\r
449         try {\r
450             return validateResponseSinglePayload(serviceResult, serviceResultsMap, expectedResponseParts, evalStruct);\r
451         } catch (Exception e){\r
452             String err = "ERROR in XmlReplay.validateResponse() : "+e;\r
453             return err  ;\r
454         }\r
455     }\r
456 \r
457     //================= runXmlReplayFile ======================================================\r
458 \r
459     public static List<ServiceResult> runXmlReplayFile(String xmlReplayBaseDir,\r
460                                           String controlFileName,\r
461                                           String testGroupID,\r
462                                           String oneTestID,\r
463                                           Map<String, ServiceResult> serviceResultsMap,\r
464                                           boolean param_autoDeletePOSTS,\r
465                                           Dump dump,\r
466                                           String protoHostPortParam,\r
467                                           AuthsMap defaultAuths,\r
468                                           List<String> reportsList,\r
469                                           String reportsDir)\r
470                                           throws Exception {\r
471         //Internally, we maintain two collections of ServiceResult:\r
472         //  the first is the return value of this method.\r
473         //  the second is the serviceResultsMap, which is used for keeping track of CSIDs created by POSTs, for later reference by DELETE, etc.\r
474         List<ServiceResult> results = new ArrayList<ServiceResult>();\r
475 \r
476         XmlReplayReport report = new XmlReplayReport(reportsDir);\r
477 \r
478         String controlFile = Tools.glue(xmlReplayBaseDir, "/", controlFileName);\r
479         org.dom4j.Document document;\r
480         document = getDocument(controlFile); //will check full path first, then checks relative to PWD.\r
481         if (document==null){\r
482             throw new FileNotFoundException("XmlReplay control file ("+controlFileName+") not found in basedir: "+xmlReplayBaseDir+" Exiting test.");\r
483         }\r
484         String protoHostPort;\r
485         if (Tools.isEmpty(protoHostPortParam)){\r
486             protoHostPort = document.selectSingleNode("/xmlReplay/protoHostPort").getText().trim();\r
487             System.out.println("DEPRECATED: Using protoHostPort ('"+protoHostPort+"') from xmlReplay file ('"+controlFile+"'), not master.");\r
488         } else {\r
489             protoHostPort = protoHostPortParam;\r
490         }\r
491         if (Tools.isEmpty(protoHostPort)){\r
492             throw new Exception("XmlReplay control file must have a protoHostPort element");\r
493         }\r
494 \r
495         String authsMapINFO;\r
496         AuthsMap authsMap = readAuths(document);\r
497         if (authsMap.map.size()==0){\r
498             authsMap = defaultAuths;\r
499             authsMapINFO = "Using defaultAuths from master file: "+defaultAuths;\r
500         } else {\r
501             authsMapINFO = "Using AuthsMap from control file: "+authsMap;\r
502         }\r
503 \r
504         report.addTestGroup(testGroupID, controlFileName);   //controlFileName is just the short name, without the full path.\r
505         String xmlReplayHeader = "========================================================================"\r
506                           +"\r\nXmlReplay running:"\r
507                           +"\r\n   controlFile: "+ (new File(controlFile).getCanonicalPath())\r
508                           +"\r\n   protoHostPort: "+protoHostPort\r
509                           +"\r\n   testGroup: "+testGroupID\r
510                           + (Tools.notEmpty(oneTestID) ? "\r\n   oneTestID: "+oneTestID : "")\r
511                           +"\r\n   AuthsMap: "+authsMapINFO\r
512                           +"\r\n   param_autoDeletePOSTS: "+param_autoDeletePOSTS\r
513                           +"\r\n   Dump info: "+dump\r
514                           +"\r\n========================================================================"\r
515                           +"\r\n";\r
516         report.addRunInfo(xmlReplayHeader);\r
517 \r
518         System.out.println(xmlReplayHeader);\r
519 \r
520         String autoDeletePOSTS = "";\r
521         List<Node> testgroupNodes;\r
522         if (Tools.notEmpty(testGroupID)){\r
523             testgroupNodes = document.selectNodes("//testGroup[@ID='"+testGroupID+"']");\r
524         } else {\r
525             testgroupNodes = document.selectNodes("//testGroup");\r
526         }\r
527 \r
528         JexlEngine jexl = new JexlEngine();   // Used for expression language expansion from uri field.\r
529         XmlReplayEval evalStruct = new XmlReplayEval();\r
530         evalStruct.serviceResultsMap = serviceResultsMap;\r
531         evalStruct.jexl = jexl;\r
532 \r
533         for (Node testgroup : testgroupNodes) {\r
534 \r
535             XmlReplayEval.MapContextWKeys jc = new XmlReplayEval.MapContextWKeys();//MapContext();  //Get a new JexlContext for each test group.\r
536             evalStruct.jc = jc;\r
537 \r
538             autoDeletePOSTS = testgroup.valueOf("@autoDeletePOSTS");\r
539             List<Node> tests;\r
540             if (Tools.notEmpty(oneTestID)){\r
541                 tests = testgroup.selectNodes("test[@ID='"+oneTestID+"']");\r
542             } else {\r
543                 tests = testgroup.selectNodes("test");\r
544             }\r
545             String authForTest = "";\r
546             int testElementIndex = -1;\r
547 \r
548             for (Node testNode : tests) {\r
549                 long startTime = System.currentTimeMillis();\r
550                 try {\r
551                     testElementIndex++;\r
552                     String testID = testNode.valueOf("@ID");\r
553                     String testIDLabel = Tools.notEmpty(testID) ? (testGroupID+'.'+testID) : (testGroupID+'.'+testElementIndex);\r
554                     String method = testNode.valueOf("method");\r
555                     String uri = testNode.valueOf("uri");\r
556                     String fullURL = Tools.glue(protoHostPort, "/", uri);\r
557 \r
558                     String authIDForTest = testNode.valueOf("@auth");\r
559                     String currentAuthForTest = authsMap.map.get(authIDForTest);\r
560                     if (Tools.notEmpty(currentAuthForTest)){\r
561                         authForTest = currentAuthForTest; //else just run with current from last loop;\r
562                     }\r
563                     if (Tools.isEmpty(authForTest)){\r
564                         authForTest = defaultAuths.getDefaultAuth();\r
565                     }\r
566 \r
567                     if (uri.indexOf("$")>-1){\r
568                         uri = evalStruct.eval(uri, serviceResultsMap, null, jexl, jc);\r
569                     }\r
570                     fullURL = fixupFullURL(fullURL, protoHostPort, uri);\r
571 \r
572                     List<Integer> expectedCodes = new ArrayList<Integer>();\r
573                     String expectedCodesStr = testNode.valueOf("expectedCodes");\r
574                     if (Tools.notEmpty(expectedCodesStr)){\r
575                          String[] codesArray = expectedCodesStr.split(",");\r
576                          for (String code : codesArray){\r
577                              expectedCodes.add(new Integer(code.trim()));\r
578                          }\r
579                     }\r
580 \r
581                     Node responseNode = testNode.selectSingleNode("response");\r
582                     PartsStruct expectedResponseParts = null;\r
583                     if (responseNode!=null){\r
584                         expectedResponseParts = PartsStruct.readParts(responseNode, testID, xmlReplayBaseDir);\r
585                         //System.out.println("reponse parts: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"+expectedResponseParts);\r
586                     }\r
587 \r
588                     ServiceResult serviceResult;\r
589                     boolean isPOST = method.equalsIgnoreCase("POST");\r
590                     boolean isPUT =  method.equalsIgnoreCase("PUT");\r
591                     if ( isPOST || isPUT ) {\r
592                         PartsStruct parts = PartsStruct.readParts(testNode, testID, xmlReplayBaseDir);\r
593                         if (Tools.notEmpty(parts.overrideTestID)) {\r
594                             testID = parts.overrideTestID;\r
595                         }\r
596                         if (isPOST){\r
597                             String csid = CSIDfromTestID(testNode, serviceResultsMap);\r
598                             if (Tools.notEmpty(csid)) uri = Tools.glue(uri, "/", csid+"/items/");\r
599                         } else if (isPUT) {\r
600                             uri = fromTestID(uri, testNode, serviceResultsMap);\r
601                         }\r
602                         //vars only make sense in two contexts: POST/PUT, because you are submitting another file with internal expressions,\r
603                         // and in <response> nodes. For GET, DELETE, there is no payload, so all the URLs with potential expressions are right there in the testNode.\r
604                         Map<String,String> vars = null;\r
605                         if (parts.varsList.size()>0){\r
606                             vars = parts.varsList.get(0);\r
607                         }\r
608                         serviceResult = XmlReplayTransport.doPOST_PUTFromXML(parts.responseFilename, vars, protoHostPort, uri, method, XmlReplayTransport.APPLICATION_XML, evalStruct, authForTest, testIDLabel);\r
609                         if (vars!=null) {\r
610                             serviceResult.addVars(vars);\r
611                         }\r
612                         results.add(serviceResult);\r
613                         //if (isPOST){\r
614                             serviceResultsMap.put(testID, serviceResult);      //PUTs do not return a Location, so don't add PUTs to serviceResultsMap.\r
615                         //}\r
616                         fullURL = fixupFullURL(fullURL, protoHostPort, uri);\r
617                     } else if (method.equalsIgnoreCase("DELETE")){\r
618                         String fromTestID = testNode.valueOf("fromTestID");\r
619                         ServiceResult pr = serviceResultsMap.get(fromTestID);\r
620                         if (pr!=null){\r
621                             serviceResult = XmlReplayTransport.doDELETE(pr.deleteURL, authForTest, testIDLabel, fromTestID);\r
622                             serviceResult.fromTestID = fromTestID;\r
623                             if (expectedCodes.size()>0){\r
624                                 serviceResult.expectedCodes = expectedCodes;\r
625                             }\r
626                             results.add(serviceResult);\r
627                             if (serviceResult.codeInSuccessRange(serviceResult.responseCode)){  //gotExpectedResult depends on serviceResult.expectedCodes.\r
628                                 serviceResultsMap.remove(fromTestID);\r
629                             }\r
630                         } else {\r
631                             if (Tools.notEmpty(fromTestID)){\r
632                                 serviceResult = new ServiceResult();\r
633                                 serviceResult.responseCode = 0;\r
634                                 serviceResult.error = "ID not found in element fromTestID: "+fromTestID;\r
635                                 System.err.println("****\r\nServiceResult: "+serviceResult.error+". SKIPPING TEST. Full URL: "+fullURL);\r
636                             } else {\r
637                                 serviceResult = XmlReplayTransport.doDELETE(fullURL, authForTest, testID, fromTestID);\r
638                             }\r
639                             serviceResult.fromTestID = fromTestID;\r
640                             results.add(serviceResult);\r
641                         }\r
642                     } else if (method.equalsIgnoreCase("GET")){\r
643                         fullURL = fromTestID(fullURL, testNode, serviceResultsMap);\r
644                         serviceResult = XmlReplayTransport.doGET(fullURL, authForTest, testIDLabel);\r
645                         results.add(serviceResult);\r
646                         serviceResultsMap.put(testID, serviceResult);\r
647                     } else if (method.equalsIgnoreCase("LIST")){\r
648                         fullURL = fixupFullURL(fullURL, protoHostPort, uri);\r
649                         String listQueryParams = ""; //TODO: empty for now, later may pick up from XML control file.\r
650                         serviceResult = XmlReplayTransport.doLIST(fullURL, listQueryParams, authForTest, testIDLabel);\r
651                         results.add(serviceResult);\r
652                         serviceResultsMap.put(testID, serviceResult);\r
653                     } else {\r
654                         throw new Exception("HTTP method not supported by XmlReplay: "+method);\r
655                     }\r
656 \r
657                     serviceResult.testID = testID;\r
658                     serviceResult.fullURL = fullURL;\r
659                     serviceResult.auth = authForTest;\r
660                     serviceResult.method = method;\r
661                     if (expectedCodes.size()>0){\r
662                         serviceResult.expectedCodes = expectedCodes;\r
663                     }\r
664                     if (Tools.isEmpty(serviceResult.testID)) serviceResult.testID = testIDLabel;\r
665                     if (Tools.isEmpty(serviceResult.testGroupID)) serviceResult.testGroupID = testGroupID;\r
666 \r
667                     Node expectedLevel = testNode.selectSingleNode("response/expected");\r
668                     if (expectedLevel!=null){\r
669                         String level = expectedLevel.valueOf("@level");\r
670                         serviceResult.payloadStrictness = level;\r
671                     }\r
672                     //=====================================================\r
673                     //  ALL VALIDATION FOR ALL REQUESTS IS DONE HERE:\r
674                     //=====================================================\r
675                     boolean hasError = false;\r
676                     String vError = validateResponse(serviceResult, serviceResultsMap, expectedResponseParts, evalStruct);\r
677                     if (Tools.notEmpty(vError)){\r
678                         serviceResult.error = vError;\r
679                         serviceResult.failureReason = " : VALIDATION ERROR; ";\r
680                         hasError = true;\r
681                     }\r
682                     if (hasError == false){\r
683                         hasError = ! serviceResult.gotExpectedResult();\r
684                     }\r
685 \r
686                     boolean doingAuto = (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.auto);\r
687                     String serviceResultRow = serviceResult.dump(dump.dumpServiceResult, hasError)+"; time:"+(System.currentTimeMillis()-startTime);\r
688                     String leader = (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.detailed) ? "XmlReplay:"+testIDLabel+": ": "";\r
689 \r
690                     report.addTestResult(serviceResult);\r
691 \r
692                     if (   (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.detailed)\r
693                         || (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.full)         ){\r
694                         System.out.println("\r\n#---------------------#");\r
695                     }\r
696                     System.out.println(timeString()+" "+leader+serviceResultRow+"\r\n");\r
697                     if (dump.payloads || (doingAuto&&hasError) ) {\r
698                         if (Tools.notBlank(serviceResult.requestPayload)){\r
699                             System.out.println("\r\n========== request payload ===============");\r
700                             System.out.println(serviceResult.requestPayload);\r
701                             System.out.println("==========================================\r\n");\r
702                         }\r
703                     }\r
704                     if (dump.payloads || (doingAuto&&hasError)) {\r
705                         if (Tools.notBlank(serviceResult.result)){\r
706                             System.out.println("\r\n========== response payload ==============");\r
707                             System.out.println(serviceResult.result);\r
708                             System.out.println("==========================================\r\n");\r
709                         }\r
710                     }\r
711                 } catch (Throwable t) {\r
712                     String msg = "ERROR: XmlReplay experienced an error in a test node: "+testNode+" Throwable: "+t;\r
713                     System.out.println(msg);\r
714                     System.out.println(Tools.getStackTrace(t));\r
715                     ServiceResult serviceResult = new ServiceResult();\r
716                     serviceResult.error = msg;\r
717                     serviceResult.failureReason = " : SYSTEM ERROR; ";\r
718                     results.add(serviceResult);\r
719                 }\r
720             }\r
721             if (Tools.isTrue(autoDeletePOSTS)&&param_autoDeletePOSTS){\r
722                 autoDelete(serviceResultsMap, "default");\r
723             }\r
724         }\r
725 \r
726         //=== Now spit out the HTML report file ===\r
727         File m = new File(controlFileName);\r
728         String localName = m.getName();//don't instantiate, just use File to extract file name without directory.\r
729         String reportName = localName+'-'+testGroupID+".html";\r
730 \r
731         File resultFile = report.saveReport(xmlReplayBaseDir, reportsDir, reportName);\r
732         if (resultFile!=null) {\r
733             String toc = report.getTOC(reportName);\r
734             reportsList.add(toc);\r
735         }\r
736         //================================\r
737 \r
738         return results;\r
739     }\r
740 \r
741                 private static String timeString() {\r
742                         java.util.Date date= new java.util.Date();\r
743                         java.sql.Timestamp ts = new java.sql.Timestamp(date.getTime());\r
744                         return ts.toString();\r
745                 }\r
746                 \r
747 \r
748     //======================== MAIN ===================================================================\r
749 \r
750     private static Options createOptions() {\r
751         Options options = new Options();\r
752         options.addOption("xmlReplayBaseDir", true, "default/basedir");\r
753         return options;\r
754     }\r
755 \r
756     public static String usage(){\r
757         String result = "org.collectionspace.services.IntegrationTests.xmlreplay.XmlReplay {args}\r\n"\r
758                         +"  -xmlReplayBaseDir <dir> \r\n"\r
759                         +" You may also override these with system args, e.g.: \r\n"\r
760                         +"   -DxmlReplayBaseDir=/path/to/dir \r\n"\r
761                         +" These may also be passed in via the POM.\r\n"\r
762                         +" You can also set these system args, e.g.: \r\n"\r
763                         +"  -DtestGroupID=<oneID> \r\n"\r
764                         +"  -DtestID=<one TestGroup ID>"\r
765                         +"  -DautoDeletePOSTS=<true|false> \r\n"\r
766                         +"    (note: -DautoDeletePOSTS won't force deletion if set to false in control file.";\r
767         return result;\r
768     }\r
769 \r
770     private static String opt(CommandLine line, String option){\r
771         String result;\r
772         String fromProps = System.getProperty(option);\r
773         if (Tools.notEmpty(fromProps)){\r
774             return fromProps;\r
775         }\r
776         if (line==null){\r
777             return "";\r
778         }\r
779         result = line.getOptionValue(option);\r
780         if (result == null){\r
781             result = "";\r
782         }\r
783         return result;\r
784     }\r
785 \r
786     public static void main(String[]args) throws Exception {\r
787         Options options = createOptions();\r
788         //System.out.println("System CLASSPATH: "+prop.getProperty("java.class.path", null));\r
789         CommandLineParser parser = new GnuParser();\r
790         try {\r
791             // parse the command line arguments\r
792             CommandLine line = parser.parse(options, args);\r
793 \r
794             String xmlReplayBaseDir = opt(line, "xmlReplayBaseDir");\r
795             String reportsDir = opt(line, "reportsDir");\r
796             String testGroupID      = opt(line, "testGroupID");\r
797             String testID           = opt(line, "testID");\r
798             String autoDeletePOSTS  = opt(line, "autoDeletePOSTS");\r
799             String dumpResults      = opt(line, "dumpResults");\r
800             String controlFilename   = opt(line, "controlFilename");\r
801             String xmlReplayMaster  = opt(line, "xmlReplayMaster");\r
802 \r
803             if (Tools.isBlank(reportsDir)){\r
804                 reportsDir = xmlReplayBaseDir + XmlReplayTest.REPORTS_DIRNAME;\r
805             }\r
806             reportsDir = Tools.fixFilename(reportsDir);\r
807             xmlReplayBaseDir = Tools.fixFilename(xmlReplayBaseDir);\r
808             controlFilename = Tools.fixFilename(controlFilename);\r
809 \r
810             boolean bAutoDeletePOSTS = true;\r
811             if (Tools.notEmpty(autoDeletePOSTS)) {\r
812                 bAutoDeletePOSTS = Tools.isTrue(autoDeletePOSTS);\r
813             }\r
814             boolean bDumpResults = false;\r
815             if (Tools.notEmpty(dumpResults)) {\r
816                 bDumpResults = Tools.isTrue(autoDeletePOSTS);\r
817             }\r
818             if (Tools.isEmpty(xmlReplayBaseDir)){\r
819                 System.err.println("ERROR: xmlReplayBaseDir was not specified.");\r
820                 return;\r
821             }\r
822             File f = new File(Tools.glue(xmlReplayBaseDir, "/", controlFilename));\r
823             if (Tools.isEmpty(xmlReplayMaster) && !f.exists()){\r
824                 System.err.println("Control file not found: "+f.getCanonicalPath());\r
825                 return;\r
826             }\r
827             File fMaster = new File(Tools.glue(xmlReplayBaseDir, "/", xmlReplayMaster));\r
828             if (Tools.notEmpty(xmlReplayMaster)  && !fMaster.exists()){\r
829                 System.err.println("Master file not found: "+fMaster.getCanonicalPath());\r
830                 return;\r
831             }\r
832 \r
833             String xmlReplayBaseDirResolved = (new File(xmlReplayBaseDir)).getCanonicalPath();\r
834             System.out.println("XmlReplay ::"\r
835                             + "\r\n    xmlReplayBaseDir: "+xmlReplayBaseDir\r
836                             + "\r\n    xmlReplayBaseDir(resolved): "+xmlReplayBaseDirResolved\r
837                             + "\r\n    controlFilename: "+controlFilename\r
838                             + "\r\n    xmlReplayMaster: "+xmlReplayMaster\r
839                             + "\r\n    testGroupID: "+testGroupID\r
840                             + "\r\n    testID: "+testID\r
841                             + "\r\n    autoDeletePOSTS: "+bAutoDeletePOSTS\r
842                             + (Tools.notEmpty(xmlReplayMaster)\r
843                                        ? ("\r\n    will use master file: "+fMaster.getCanonicalPath())\r
844                                        : ("\r\n    will use control file: "+f.getCanonicalPath()) )\r
845                              );\r
846             \r
847             if (Tools.notEmpty(xmlReplayMaster)){\r
848                 if (Tools.notEmpty(controlFilename)){\r
849                     System.out.println("WARN: controlFilename: "+controlFilename+" will not be used because master was specified.  Running master: "+xmlReplayMaster);\r
850                 }\r
851                 XmlReplay replay = new XmlReplay(xmlReplayBaseDirResolved, reportsDir);\r
852                 replay.readOptionsFromMasterConfigFile(xmlReplayMaster);\r
853                 replay.setAutoDeletePOSTS(bAutoDeletePOSTS);\r
854                 Dump dumpFromMaster = replay.getDump();\r
855                 dumpFromMaster.payloads = Tools.isTrue(dumpResults);\r
856                 replay.setDump(dumpFromMaster);\r
857                 replay.runMaster(xmlReplayMaster, false); //false, because we already just read the options, and override a few.\r
858             } else {\r
859                 Dump dump = getDumpConfig();\r
860                 dump.payloads = Tools.isTrue(dumpResults);\r
861                 List<String> reportsList = new ArrayList<String>();\r
862                 runXmlReplayFile(xmlReplayBaseDirResolved, controlFilename, testGroupID, testID, createResultsMap(), bAutoDeletePOSTS, dump, "", null, reportsList, reportsDir);\r
863                 System.out.println("DEPRECATED: reportsList is generated, but not dumped: "+reportsList.toString());\r
864             }\r
865         } catch (ParseException exp) {\r
866             // oops, something went wrong\r
867             System.err.println("Cmd-line parsing failed.  Reason: " + exp.getMessage());\r
868             System.err.println(usage());\r
869         } catch (Exception e) {\r
870             System.out.println("Error : " + e.getMessage());\r
871             e.printStackTrace();\r
872         }\r
873     }\r
874 \r
875 }\r