1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 /* 19 * $Id$ 20 */ 21 22 /* 23 * 24 * TransformStateTestlet.java 25 * 26 */ 27 package org.apache.qetest.xalanj2; 28 29 import java.util.Hashtable; 30 31 import javax.xml.transform.Transformer; 32 import javax.xml.transform.TransformerFactory; 33 import javax.xml.transform.sax.SAXResult; 34 import javax.xml.transform.stream.StreamSource; 35 36 import org.apache.qetest.Datalet; 37 import org.apache.qetest.Logger; 38 import org.apache.qetest.LoggingHandler; 39 import org.apache.qetest.QetestUtils; 40 import org.apache.qetest.TestletImpl; 41 import org.apache.qetest.XMLFileLogger; 42 import org.apache.xalan.templates.ElemTemplate; 43 import org.apache.xalan.templates.ElemTemplateElement; 44 import org.apache.xalan.transformer.TransformState; 45 import org.apache.xalan.transformer.TransformerClient; 46 import org.w3c.dom.Node; 47 import org.w3c.dom.traversal.NodeIterator; 48 import org.xml.sax.Attributes; 49 import org.xml.sax.ContentHandler; 50 import org.xml.sax.Locator; 51 import org.xml.sax.SAXException; 52 53 /** 54 * Testlet for testing TransformState of a stylesheet. 55 * 56 * In progress - data-driven tests for tooling API's. 57 * Currently uses cheap-o validation method 58 * 59 * @author Shane_Curcuru@lotus.com 60 * @version $Id$ 61 */ 62 public class TransformStateTestlet extends TestletImpl 63 implements ContentHandler, TransformerClient 64 65 { 66 // Initialize our classname for TestletImpl's main() method 67 static { thisClassName = "org.apache.qetest.xsl.TransformStateTestlet"; } 68 69 // Initialize our defaultDatalet 70 { defaultDatalet = (Datalet)new TransformStateDatalet(); } 71 72 /** 73 * Class-wide copy of TransformStateDatalet. 74 * This is used in execute() and in various worker methods 75 * underneath the ContentHandler interface. 76 */ 77 protected TransformStateDatalet tsDatalet = null; 78 79 /** 80 * Accesor method for a brief description of this test. 81 * 82 * @return String describing what this TransformStateTestlet does. 83 */ getDescription()84 public String getDescription() 85 { 86 return "TransformStateTestlet"; 87 } 88 89 90 /** 91 * Run this TransformStateTestlet: execute it's test and return. 92 * 93 * @param Datalet to use as data point for the test. 94 */ execute(Datalet d)95 public void execute(Datalet d) 96 { 97 try 98 { 99 tsDatalet = (TransformStateDatalet)d; 100 } 101 catch (ClassCastException e) 102 { 103 logger.checkErr("Datalet provided is not a TransformStateDatalet; cannot continue with " + d); 104 return; 105 } 106 107 logger.logMsg(Logger.STATUSMSG, "About to test: " 108 + (null == tsDatalet.inputName 109 ? tsDatalet.xmlName 110 : tsDatalet.inputName)); 111 try 112 { 113 // Perform the transform 114 TransformerFactory factory = TransformerFactory.newInstance(); 115 logger.logMsg(Logger.TRACEMSG, "---- About to newTransformer " + QetestUtils.filenameToURL(tsDatalet.inputName)); 116 Transformer transformer = factory.newTransformer(new StreamSource(QetestUtils.filenameToURL(tsDatalet.inputName))); 117 logger.logMsg(Logger.TRACEMSG, "---- About to transform " + QetestUtils.filenameToURL(tsDatalet.xmlName) + " into: SAXResult(this-no disk output)"); 118 119 // Note most validation happens here: we get ContentHandler 120 // callbacks from being in the SAXResult, and that's where 121 // we do our validation 122 transformer.transform(new StreamSource(QetestUtils.filenameToURL(tsDatalet.xmlName)), 123 new SAXResult(this)); // use us to handle result 124 125 logger.logMsg(Logger.INFOMSG, "---- Afterwards, this.transformState=" + transformState); 126 } 127 catch (Throwable t) 128 { 129 // Put the logThrowable first, so it appears before 130 // the Fail record, and gets color-coded 131 logger.logThrowable(Logger.ERRORMSG, t, getDescription() + " " + tsDatalet.getDescription()); 132 logger.checkFail(getDescription() + " " + tsDatalet.getDescription() 133 + " threw: " + t.toString()); 134 return; 135 } 136 } 137 ////////////////// partially Implement LoggingHandler ////////////////// 138 /** Cheap-o string representation of last event we got. */ 139 protected String lastItem = LoggingHandler.NOTHING_HANDLED; 140 141 142 /** 143 * Accessor for string representation of last event we got. 144 * @param s string to set 145 */ setLastItem(String s)146 protected void setLastItem(String s) 147 { 148 lastItem = s; 149 } 150 151 152 /** 153 * Accessor for string representation of last event we got. 154 * @return last event string we had 155 */ getLast()156 public String getLast() 157 { 158 return lastItem; 159 } 160 161 /** 162 * Worker routine to validate a TransformState based on an event/value. 163 * Note: this may not be threadsafe! 164 * //@todo actually add validation code - just logs out now 165 * @param ts TransformState to validate, if null, just logs it 166 * @param event our String constant of START_ELEMENT, etc. 167 * @param value any String value of the current event 168 */ validateTransformState(TransformState ts, String event, String value)169 protected void validateTransformState(TransformState ts, String event, String value) 170 { 171 if(null == transformState) 172 { 173 // We should never have a null TransformState since the 174 // transformer should have always filled it in 175 logger.checkErr("validateTransformState(ts-NULL!, " + event + ")=" + value); 176 return; 177 } 178 logTransformStateDump(logger, Logger.INFOMSG, ts, event, value); 179 180 // Cheap-o validation: only validate items on column 99 181 if (99 == ts.getCurrentElement().getColumnNumber()) 182 { 183 int line = ts.getCurrentElement().getLineNumber(); 184 // Get cheap-o validation from the datalet for this line.. 185 String exp = (String)tsDatalet.validate99.get(line + ".current.name"); 186 // .. If there's an expected value for this line's property.. 187 if (null != exp) 188 // .. Then check if it's equal and report pass/fail 189 checkString(ts.getCurrentTemplate().getName().toString(), exp, 190 "Validate L" + line + "C99 .current.name"); 191 192 exp = (String)tsDatalet.validate99.get(line + ".current.match"); 193 if (null != exp) 194 checkString(ts.getCurrentTemplate().getMatch().getPatternString(), exp, 195 "Validate L" + line + "C99 .current.match"); 196 197 exp = (String)tsDatalet.validate99.get(line + ".current.mode"); 198 if (null != exp) 199 checkString(ts.getCurrentTemplate().getMode().toString(), exp, 200 "Validate L" + line + "C99 .current.mode"); 201 202 203 exp = (String)tsDatalet.validate99.get(line + ".matched.name"); 204 if (null != exp) 205 checkString(ts.getMatchedTemplate().getName().toString(), exp, 206 "Validate L" + line + "C99 .matched.name"); 207 208 exp = (String)tsDatalet.validate99.get(line + ".matched.match"); 209 if (null != exp) 210 checkString(ts.getMatchedTemplate().getMatch().getPatternString(), exp, 211 "Validate L" + line + "C99 .matched.match"); 212 213 exp = (String)tsDatalet.validate99.get(line + ".matched.mode"); 214 if (null != exp) 215 checkString(ts.getMatchedTemplate().getMode().toString(), exp, 216 "Validate L" + line + "C99 .matched.mode"); 217 } 218 219 /*********************************************** 220 // Comment out validation using ExpectedObjects since they hang 28-Jun-01 -sc 221 // See if we have a matching expected state for this event 222 //@todo use event string as part of hashkey!!! 223 String marker = ExpectedTransformState.getHashKey(ts); 224 // Add on the event as well; see ExpectedTransformState.getHashKey() 225 // for why we have to do this separately 226 marker += ExpectedTransformState.SEP + event; 227 ExpectedTransformState ets = (ExpectedTransformState)tsDatalet.expectedTransformStates.get(marker); 228 logger.logMsg(Logger.TRACEMSG, "ETS-HACK:" + marker + "=" + ets); 229 if (null != ets) 230 { 231 // Ask it to validate itself as needed 232 synchronized(ets) // voodoo: attempt to solve hang problems 233 { 234 ExpectedObjectCheckService.check(logger, ts, ets, "Compare ExpectedTransformState of " + marker); 235 } 236 } 237 // Comment out validation using ExpectedObjects since they hang 28-Jun-01 -sc 238 ***********************************************/ 239 } 240 checkString(String act, String exp, String comment)241 private void checkString(String act, String exp, String comment) 242 { 243 if (exp.equals(act)) 244 logger.checkPass(comment); 245 else 246 logger.checkFail(comment + "; act(" + act + ") exp(" + exp + ")"); 247 } 248 249 ////////////////// Utility methods for TransformState ////////////////// 250 /** 251 * Utility method to dump data from TransformState. 252 * @return String describing various bits of the state 253 */ logTransformStateDump(Logger logger, int traceLoggingLevel, TransformState ts, String event, String value)254 protected void logTransformStateDump(Logger logger, int traceLoggingLevel, 255 TransformState ts, String event, String value) 256 { 257 String elemName = "transformStateDump"; 258 Hashtable attrs = new Hashtable(); 259 attrs.put("event", event); 260 if (null != value) 261 attrs.put("value", value); 262 attrs.put("location", "L" + ts.getCurrentElement().getLineNumber() 263 + "C" + ts.getCurrentElement().getColumnNumber()); 264 265 StringBuffer buf = new StringBuffer(); 266 ElemTemplateElement elem = ts.getCurrentElement(); // may be actual or default template 267 buf.append(" <currentElement>" 268 + XMLFileLogger.escapeString(XalanDumper.dump(elem, XalanDumper.DUMP_DEFAULT)) + "</currentElement>"); 269 270 ElemTemplate currentTempl = ts.getCurrentTemplate(); // Actual current template 271 buf.append("\n <currentTemplate>" 272 + XMLFileLogger.escapeString(XalanDumper.dump(currentTempl, XalanDumper.DUMP_DEFAULT)) + "</currentTemplate>"); 273 274 ElemTemplate matchTempl = ts.getMatchedTemplate(); // Actual matched template 275 if (matchTempl != currentTempl) 276 buf.append("\n <matchedTemplate>" 277 + XMLFileLogger.escapeString(XalanDumper.dump(matchTempl, XalanDumper.DUMP_DEFAULT)) + "</matchedTemplate>"); 278 279 // Optimization: skip most logging when on endElement 280 if (!END_ELEMENT.equals(event)) 281 { 282 Node n = ts.getCurrentNode(); // current context node in source tree 283 buf.append("\n <currentNode>" 284 + XMLFileLogger.escapeString(XalanDumper.dump(n, XalanDumper.DUMP_DEFAULT)) + "</currentNode>"); 285 286 Node matchedNode = ts.getMatchedNode(); // node in source matched via getMatchedTemplate 287 // Optimization: only output if different 288 if (n != matchedNode) 289 buf.append("\n <matchedNode>" 290 + XMLFileLogger.escapeString(XalanDumper.dump(matchedNode, XalanDumper.DUMP_DEFAULT)) + "</matchedNode>"); 291 292 NodeIterator contextNodeList = ts.getContextNodeList(); // current context node list 293 Node rootNode = contextNodeList.getRoot(); 294 // Optimization: only output if different 295 if (n != rootNode) 296 buf.append("\n <contextNodeListGetRoot>" 297 + XMLFileLogger.escapeString(XalanDumper.dump(rootNode, XalanDumper.DUMP_DEFAULT)) + "</contextNodeListGetRoot>"); 298 299 Transformer transformer = ts.getTransformer(); // current transformer working 300 // Optimization: only dump transformer at startElement to save space 301 if (START_ELEMENT.equals(event)) 302 { 303 buf.append("\n <transformer>" 304 + XMLFileLogger.escapeString(XalanDumper.dump(transformer, XalanDumper.DUMP_DEFAULT)) + "</transformer>"); 305 } 306 else 307 { 308 // Just log error case if transformer is ever null 309 if (null == transformer) { 310 buf.append("\n <transformer>" 311 + "ERROR! Transformer was null!" + "</transformer>"); 312 } 313 } 314 } 315 316 logger.logElement(traceLoggingLevel, elemName, attrs, buf.toString()); 317 } 318 319 //----------------------------------------------------------- 320 //---- Implement the TransformerClient interface 321 //----------------------------------------------------------- 322 /** 323 * A TransformState object that we use to log state data. 324 * This is the equivalent of the defaultHandler, even though 325 * that's not really the right metaphor. This class could be 326 * upgraded to have both a default ContentHandler and a 327 * defaultTransformerClient in the future. 328 */ 329 protected TransformState transformState = null; 330 331 332 /** 333 * Implement TransformerClient.setTransformState interface. 334 * Pass in a reference to a TransformState object, which 335 * can be used during SAX ContentHandler events to obtain 336 * information about he state of the transformation. This 337 * method will be called before each startDocument event. 338 * 339 * @param ts A reference to a TransformState object 340 */ setTransformState(TransformState ts)341 public void setTransformState(TransformState ts) 342 { 343 transformState = ts; 344 } 345 346 //----------------------------------------------------------- 347 //---- Implement the ContentHandler interface 348 //----------------------------------------------------------- 349 protected final String START_ELEMENT = "startElement:"; 350 protected final String END_ELEMENT = "endElement:"; 351 protected final String CHARACTERS = "characters:"; 352 353 // String Locator.getPublicId() null if none available 354 // String Locator.getPublicId() null if none available 355 // int Locator.getLineNumber() -1 if none available 356 // int Locator.getColumnNumber() -1 if none available 357 protected Locator ourLocator = null; 358 359 /** 360 * Implement ContentHandler.setDocumentLocator. 361 * If available, this should always be called prior to a 362 * startDocument event. 363 */ setDocumentLocator(Locator locator)364 public void setDocumentLocator (Locator locator) 365 { 366 // Note: this implies this class is !not! threadsafe 367 ourLocator = locator; // future use 368 if (null != locator) 369 setLastItem("setDocumentLocator.getSystemId():" + locator.getSystemId()); 370 else 371 setLastItem("setDocumentLocator:NULL"); 372 logger.logMsg(Logger.INFOMSG, getLast()); 373 } 374 375 376 /** Cached TransformState object during lifetime startDocument -> endDocument. */ 377 // Note: is this correct? Will it always be the same object? 378 protected TransformState docCachedTransformState = null; 379 /** Implement ContentHandler.startDocument. */ startDocument()380 public void startDocument () 381 throws SAXException 382 { 383 setLastItem("startDocument"); 384 logger.logMsg(Logger.INFOMSG, getLast()); 385 // Comment out check call since the spec'd functionality 386 // is very likely to change to *not* be in startDocument 19-Jun-01 -sc 387 // logger.check((null != transformState), true, "transformState non-null in startDocument"); 388 logger.logMsg(Logger.STATUSMSG, "transformState in startDocument is: " + transformState); 389 docCachedTransformState = transformState; // see endDocument 390 } 391 392 393 /** Implement ContentHandler.endDocument. */ endDocument()394 public void endDocument() 395 throws SAXException 396 { 397 setLastItem("endDocument"); 398 logger.logMsg(Logger.INFOMSG, getLast()); 399 // Comment out check call since the spec'd functionality 400 // is very likely to change to *not* be in startDocument 19-Jun-01 -sc 401 // logger.checkObject(docCachedTransformState, transformState, 402 // "transformState same in endDocument as startDocument"); // see startDocument 403 logger.logMsg(Logger.STATUSMSG, "transformState in endDocument is: " + transformState); 404 docCachedTransformState = null; 405 } 406 407 408 /** Implement ContentHandler.startPrefixMapping. */ startPrefixMapping(String prefix, String uri)409 public void startPrefixMapping (String prefix, String uri) 410 throws SAXException 411 { 412 setLastItem("startPrefixMapping: " + prefix + ", " + uri); 413 logger.logMsg(Logger.INFOMSG, getLast()); 414 } 415 416 417 /** Implement ContentHandler.endPrefixMapping. */ endPrefixMapping(String prefix)418 public void endPrefixMapping (String prefix) 419 throws SAXException 420 { 421 setLastItem("endPrefixMapping: " + prefix); 422 logger.logMsg(Logger.INFOMSG, getLast()); 423 } 424 425 426 /** Implement ContentHandler.startElement. */ startElement(String namespaceURI, String localName, String qName, Attributes atts)427 public void startElement (String namespaceURI, String localName, 428 String qName, Attributes atts) 429 throws SAXException 430 { 431 StringBuffer buf = new StringBuffer(); 432 buf.append(namespaceURI + ", " 433 + localName + ", " + qName + ";"); 434 435 int n = atts.getLength(); 436 for(int i = 0; i < n; i++) 437 { 438 buf.append(", " + atts.getQName(i)); 439 } 440 setLastItem(START_ELEMENT + buf.toString()); 441 442 validateTransformState(transformState, START_ELEMENT, buf.toString()); 443 } 444 445 446 /** Implement ContentHandler.endElement. */ endElement(String namespaceURI, String localName, String qName)447 public void endElement (String namespaceURI, String localName, String qName) 448 throws SAXException 449 { 450 setLastItem(END_ELEMENT + namespaceURI + ", " + localName + ", " + qName); 451 452 validateTransformState(transformState, END_ELEMENT, null); 453 } 454 455 456 /** Implement ContentHandler.characters. */ characters(char ch[], int start, int length)457 public void characters (char ch[], int start, int length) 458 throws SAXException 459 { 460 String s = new String(ch, start, length); 461 setLastItem(CHARACTERS + "\"" + s + "\""); 462 463 validateTransformState(transformState, CHARACTERS, s); 464 } 465 466 467 /** Implement ContentHandler.ignorableWhitespace. */ ignorableWhitespace(char ch[], int start, int length)468 public void ignorableWhitespace (char ch[], int start, int length) 469 throws SAXException 470 { 471 setLastItem("ignorableWhitespace: len " + length); 472 logger.logMsg(Logger.INFOMSG, getLast()); 473 } 474 475 476 /** Implement ContentHandler.processingInstruction. */ processingInstruction(String target, String data)477 public void processingInstruction (String target, String data) 478 throws SAXException 479 { 480 setLastItem("processingInstruction: " + target + ", " + data); 481 logger.logMsg(Logger.INFOMSG, getLast()); 482 } 483 484 485 /** Implement ContentHandler.skippedEntity. */ skippedEntity(String name)486 public void skippedEntity (String name) 487 throws SAXException 488 { 489 setLastItem("skippedEntity: " + name); 490 logger.logMsg(Logger.INFOMSG, getLast()); 491 } 492 493 } // end of class TransformStateTestlet 494 495