001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.scxml2.io; 018 019import java.io.IOException; 020import java.io.StringReader; 021import java.util.ArrayList; 022import java.util.LinkedList; 023import java.util.List; 024 025import javax.xml.stream.XMLStreamException; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogConfigurationException; 029import org.apache.commons.logging.LogFactory; 030import org.apache.commons.logging.impl.LogFactoryImpl; 031import org.apache.commons.logging.impl.SimpleLog; 032import org.apache.commons.scxml2.ActionExecutionContext; 033import org.apache.commons.scxml2.SCXMLExpressionException; 034import org.apache.commons.scxml2.SCXMLTestHelper; 035import org.apache.commons.scxml2.io.SCXMLReader.Configuration; 036import org.apache.commons.scxml2.model.Action; 037import org.apache.commons.scxml2.model.CustomAction; 038import org.apache.commons.scxml2.model.Data; 039import org.apache.commons.scxml2.model.Datamodel; 040import org.apache.commons.scxml2.model.EnterableState; 041import org.apache.commons.scxml2.model.ExternalContent; 042import org.apache.commons.scxml2.model.Final; 043import org.apache.commons.scxml2.model.ModelException; 044import org.apache.commons.scxml2.model.SCXML; 045import org.apache.commons.scxml2.model.Send; 046import org.apache.commons.scxml2.model.State; 047import org.apache.commons.scxml2.model.Transition; 048import org.junit.After; 049import org.junit.AfterClass; 050import org.junit.Assert; 051import org.junit.Before; 052import org.junit.BeforeClass; 053import org.junit.Test; 054import org.w3c.dom.Node; 055 056/** 057 * Unit tests {@link org.apache.commons.scxml2.io.SCXMLReader}. 058 */ 059public class SCXMLReaderTest { 060 061 private static String oldLogFactoryProperty; 062 063 private Log scxmlReaderLog; 064 065 @BeforeClass 066 public static void beforeClass() { 067 oldLogFactoryProperty = System.getProperty(LogFactory.FACTORY_PROPERTY); 068 System.setProperty(LogFactory.FACTORY_PROPERTY, RecordingLogFactory.class.getName()); 069 } 070 071 @AfterClass 072 public static void afterClass() { 073 if (oldLogFactoryProperty == null) { 074 System.clearProperty(LogFactory.FACTORY_PROPERTY); 075 } else { 076 System.setProperty(LogFactory.FACTORY_PROPERTY, oldLogFactoryProperty); 077 } 078 } 079 080 /** 081 * Set up instance variables required by this test case. 082 */ 083 @Before 084 public void before() { 085 scxmlReaderLog = LogFactory.getLog(SCXMLReader.class); 086 clearRecordedLogMessages(); 087 } 088 089 /** 090 * Test the implementation 091 */ 092 @Test 093 public void testSCXMLReaderMicrowave03Sample() throws Exception { 094 SCXML scxml = SCXMLTestHelper.parse("org/apache/commons/scxml2/env/jexl/microwave-03.xml"); 095 Assert.assertNotNull(scxml); 096 Assert.assertNotNull(serialize(scxml)); 097 } 098 099 @Test 100 public void testSCXMLReaderMicrowave04Sample() throws Exception { 101 SCXML scxml = SCXMLTestHelper.parse("org/apache/commons/scxml2/env/jexl/microwave-04.xml"); 102 Assert.assertNotNull(scxml); 103 Assert.assertNotNull(serialize(scxml)); 104 } 105 106 @Test 107 public void testSCXMLReaderTransitions01Sample() throws Exception { 108 SCXML scxml = SCXMLTestHelper.parse("org/apache/commons/scxml2/transitions-01.xml"); 109 Assert.assertNotNull(scxml); 110 Assert.assertNotNull(serialize(scxml)); 111 } 112 113 @Test 114 public void testSCXMLReaderPrefix01Sample() throws Exception { 115 SCXML scxml = SCXMLTestHelper.parse("org/apache/commons/scxml2/prefix-01.xml"); 116 Assert.assertNotNull(scxml); 117 Assert.assertNotNull(serialize(scxml)); 118 } 119 120 @Test 121 public void testSCXMLReaderSend01Sample() throws Exception { 122 SCXML scxml = SCXMLTestHelper.parse("org/apache/commons/scxml2/send-01.xml"); 123 State ten = (State) scxml.getInitialTransition().getTargets().iterator().next(); 124 Assert.assertEquals("ten", ten.getId()); 125 List<Transition> ten_done = ten.getTransitionsList("done.state.ten"); 126 Assert.assertEquals(1, ten_done.size()); 127 Transition ten2twenty = ten_done.get(0); 128 List<Action> actions = ten2twenty.getActions(); 129 Assert.assertEquals(1, actions.size()); 130 Send send = (Send) actions.get(0); 131 Assert.assertEquals("send1", send.getId()); 132 /* Serialize 133 scxmlAsString = serialize(scxml); 134 Assert.assertNotNull(scxmlAsString); 135 String expectedFoo2Serialization = 136 "<foo xmlns=\"http://my.test.namespace\" id=\"foo2\">" 137 + "<prompt xmlns=\"http://foo.bar.com/vxml3\">This is just" 138 + " an example.</prompt></foo>"; 139 Assert.assertFalse(scxmlAsString.indexOf(expectedFoo2Serialization) == -1); 140 */ 141 } 142 143 @Test 144 public void testSCXMLReaderInitialAttr() throws Exception { 145 SCXML scxml = SCXMLTestHelper.parse("org/apache/commons/scxml2/io/scxml-initial-attr.xml"); 146 Assert.assertNotNull(scxml); 147 Assert.assertNotNull(serialize(scxml)); 148 Final foo = (Final) scxml.getInitialTransition().getTargets().iterator().next(); 149 Assert.assertEquals("foo", foo.getId()); 150 } 151 152 @Test 153 public void testSCXMLValidTransitionTargets() throws Exception { 154 // ModelUpdater will fail on invalid transition targets 155 SCXMLTestHelper.parse(SCXMLTestHelper.getResource("org/apache/commons/scxml2/io/scxml-valid-transition-targets-test.xml")); 156 } 157 158 @Test(expected=org.apache.commons.scxml2.model.ModelException.class) 159 public void testSCXMLInValidTransitionTargets1() throws Exception { 160 // ModelUpdater will fail on invalid transition targets 161 SCXMLTestHelper.parse(SCXMLTestHelper.getResource("org/apache/commons/scxml2/io/scxml-invalid-transition-targets-test1.xml")); 162 } 163 164 @Test(expected=org.apache.commons.scxml2.model.ModelException.class) 165 public void testSCXMLInValidTransitionTargets2() throws Exception { 166 // ModelUpdater will fail on invalid transition targets 167 SCXMLTestHelper.parse(SCXMLTestHelper.getResource("org/apache/commons/scxml2/io/scxml-invalid-transition-targets-test2.xml")); 168 } 169 170 @Test 171 public void testSCXMLReaderCustomActionWithBodyTextSample() throws Exception { 172 List<CustomAction> cas = new ArrayList<CustomAction>(); 173 CustomAction ca = new CustomAction("http://my.custom-actions.domain", 174 "action", MyAction.class); 175 cas.add(ca); 176 SCXML scxml = SCXMLTestHelper.parse("org/apache/commons/scxml2/io/custom-action-body-test-1.xml", cas); 177 EnterableState state = (EnterableState) scxml.getInitialTransition().getTargets().iterator().next(); 178 Assert.assertEquals("actions", state.getId()); 179 List<Action> actions = state.getOnEntries().get(0).getActions(); 180 Assert.assertEquals(1, actions.size()); 181 MyAction my = (MyAction) actions.get(0); 182 Assert.assertNotNull(my); 183 Assert.assertTrue(my.getExternalNodes().size() > 0); 184 } 185 186 @Test 187 public void testSCXMLReaderWithInvalidElements() throws Exception { 188 // In the default lenient/verbose mode (strict == false && silent == false), 189 // the model exception should be just logged without a model exception. 190 Configuration configuration = new Configuration(); 191 SCXML scxml = SCXMLReader.read(SCXMLTestHelper.getResource("org/apache/commons/scxml2/io/scxml-with-invalid-elems.xml"), 192 configuration); 193 Assert.assertNotNull(scxml); 194 Assert.assertNotNull(serialize(scxml)); 195 Final foo = (Final) scxml.getInitialTransition().getTargets().iterator().next(); 196 Assert.assertEquals("foo", foo.getId()); 197 Datamodel dataModel = scxml.getDatamodel(); 198 Assert.assertNotNull(dataModel); 199 List<Data> dataList = dataModel.getData(); 200 Assert.assertEquals(1, dataList.size()); 201 Assert.assertEquals("time", dataList.get(0).getId()); 202 assertContainsRecordedLogMessage("Ignoring unknown or invalid element <baddata> in namespace \"http://www.w3.org/2005/07/scxml\" as child of <datamodel>"); 203 assertContainsRecordedLogMessage("Ignoring unknown or invalid element <baddata> in namespace \"http://www.example.com/scxml\" as child of <datamodel>"); 204 assertContainsRecordedLogMessage("Ignoring unknown or invalid element <trace> in namespace \"http://www.w3.org/2005/07/scxml\" as child of <onentry>"); 205 assertContainsRecordedLogMessage("Ignoring unknown or invalid element <onbeforeexit> in namespace \"http://www.w3.org/2005/07/scxml\" as child of <final>"); 206 207 // In the lenient/silent mode (strict == false && silent == true), 208 // no model exception is logged. 209 clearRecordedLogMessages(); 210 scxml = null; 211 configuration = new Configuration(); 212 configuration.setStrict(false); 213 configuration.setSilent(true); 214 scxml = SCXMLReader.read(SCXMLTestHelper.getResource("org/apache/commons/scxml2/io/scxml-with-invalid-elems.xml"), 215 configuration); 216 Assert.assertNotNull(scxml); 217 Assert.assertNotNull(serialize(scxml)); 218 foo = (Final) scxml.getInitialTransition().getTargets().iterator().next(); 219 Assert.assertEquals("foo", foo.getId()); 220 dataModel = scxml.getDatamodel(); 221 Assert.assertNotNull(dataModel); 222 dataList = dataModel.getData(); 223 Assert.assertEquals(1, dataList.size()); 224 Assert.assertEquals("time", dataList.get(0).getId()); 225 assertNotContainsRecordedLogMessage("Ignoring unknown or invalid element <baddata> in namespace \"http://www.w3.org/2005/07/scxml\" as child of <datamodel>"); 226 assertNotContainsRecordedLogMessage("Ignoring unknown or invalid element <baddata> in namespace \"http://www.example.com/scxml\" as child of <datamodel>"); 227 assertNotContainsRecordedLogMessage("Ignoring unknown or invalid element <trace> in namespace \"http://www.w3.org/2005/07/scxml\" as child of <onentry>"); 228 assertNotContainsRecordedLogMessage("Ignoring unknown or invalid element <onbeforeexit> in namespace \"http://www.w3.org/2005/07/scxml\" as child of <final>"); 229 230 // In strict/verbose mode (strict == true && silent == false), it should fail to read the model and catch a model exception 231 // with warning logs because of the invalid <baddata> element. 232 clearRecordedLogMessages(); 233 scxml = null; 234 configuration = new Configuration(); 235 configuration.setStrict(true); 236 configuration.setSilent(false); 237 try { 238 scxml = SCXMLReader.read(SCXMLTestHelper.getResource("org/apache/commons/scxml2/io/scxml-with-invalid-elems.xml"), 239 configuration); 240 Assert.fail("In strict mode, it should have thrown a model exception."); 241 } catch (ModelException e) { 242 Assert.assertTrue(e.getMessage().contains("Ignoring unknown or invalid element <baddata>")); 243 } 244 assertContainsRecordedLogMessage("Ignoring unknown or invalid element <baddata> in namespace \"http://www.w3.org/2005/07/scxml\" as child of <datamodel>"); 245 assertContainsRecordedLogMessage("Ignoring unknown or invalid element <baddata> in namespace \"http://www.example.com/scxml\" as child of <datamodel>"); 246 assertContainsRecordedLogMessage("Ignoring unknown or invalid element <trace> in namespace \"http://www.w3.org/2005/07/scxml\" as child of <onentry>"); 247 assertContainsRecordedLogMessage("Ignoring unknown or invalid element <onbeforeexit> in namespace \"http://www.w3.org/2005/07/scxml\" as child of <final>"); 248 249 // In strict/silent mode (strict == true && silent == true), it should fail to read the model and catch a model exception 250 // without warning logs because of the invalid <baddata> element. 251 clearRecordedLogMessages(); 252 scxml = null; 253 configuration = new Configuration(); 254 configuration.setStrict(true); 255 configuration.setSilent(true); 256 try { 257 scxml = SCXMLReader.read(SCXMLTestHelper.getResource("org/apache/commons/scxml2/io/scxml-with-invalid-elems.xml"), 258 configuration); 259 Assert.fail("In strict mode, it should have thrown a model exception."); 260 } catch (ModelException e) { 261 Assert.assertTrue(e.getMessage().contains("Ignoring unknown or invalid element <baddata>")); 262 } 263 assertNotContainsRecordedLogMessage("Ignoring unknown or invalid element <baddata> in namespace \"http://www.w3.org/2005/07/scxml\" as child of <datamodel>"); 264 assertNotContainsRecordedLogMessage("Ignoring unknown or invalid element <baddata> in namespace \"http://www.example.com/scxml\" as child of <datamodel>"); 265 assertNotContainsRecordedLogMessage("Ignoring unknown or invalid element <trace> in namespace \"http://www.w3.org/2005/07/scxml\" as child of <onentry>"); 266 assertNotContainsRecordedLogMessage("Ignoring unknown or invalid element <onbeforeexit> in namespace \"http://www.w3.org/2005/07/scxml\" as child of <final>"); 267 } 268 269 @Test 270 public void testSCXMLReaderGroovyClosure() throws Exception { 271 SCXML scxml = SCXMLTestHelper.parse("org/apache/commons/scxml2/env/groovy/groovy-closure.xml"); 272 Assert.assertNotNull(scxml); 273 Assert.assertNotNull(scxml.getGlobalScript()); 274 String scxmlAsString = serialize(scxml); 275 Assert.assertNotNull(scxmlAsString); 276 scxml = SCXMLTestHelper.parse(new StringReader(scxmlAsString), null); 277 Assert.assertNotNull(scxml); 278 Assert.assertNotNull(scxml.getGlobalScript()); 279 } 280 281 private String serialize(final SCXML scxml) throws IOException, XMLStreamException { 282 String scxmlAsString = SCXMLWriter.write(scxml); 283 Assert.assertNotNull(scxmlAsString); 284 return scxmlAsString; 285 } 286 287 private void assertContainsRecordedLogMessage(final String message) { 288 if (scxmlReaderLog instanceof RecordingSimpleLog) { 289 Assert.assertTrue(((RecordingSimpleLog) scxmlReaderLog).containsMessage( 290 "Ignoring unknown or invalid element <baddata> in namespace \"http://www.w3.org/2005/07/scxml\" as child of <datamodel>")); 291 } 292 } 293 294 private void assertNotContainsRecordedLogMessage(final String message) { 295 if (scxmlReaderLog instanceof RecordingSimpleLog) { 296 Assert.assertFalse(((RecordingSimpleLog) scxmlReaderLog).containsMessage( 297 "Ignoring unknown or invalid element <baddata> in namespace \"http://www.w3.org/2005/07/scxml\" as child of <datamodel>")); 298 } 299 } 300 301 private void clearRecordedLogMessages() { 302 if (scxmlReaderLog instanceof RecordingSimpleLog) { 303 ((RecordingSimpleLog) scxmlReaderLog).clearMessages(); 304 } 305 } 306 307 public static class MyAction extends Action implements ExternalContent { 308 private static final long serialVersionUID = 1L; 309 310 private List<Node> nodes = new ArrayList<Node>(); 311 312 @Override 313 public void execute(ActionExecutionContext exctx) throws ModelException, SCXMLExpressionException { 314 // Not relevant to test 315 } 316 317 @Override 318 public List<Node> getExternalNodes() { 319 return nodes; 320 } 321 322 } 323 324 /** 325 * Custom LogFactory implementation to capture log messages for logging verification. 326 */ 327 public static class RecordingLogFactory extends LogFactoryImpl { 328 @Override 329 protected Log newInstance(String name) throws LogConfigurationException { 330 return new RecordingSimpleLog(name); 331 } 332 } 333 334 /** 335 * Custom Simple Log implemenation capturing log messages 336 */ 337 public static class RecordingSimpleLog extends SimpleLog { 338 339 private static final long serialVersionUID = 1L; 340 341 private List<String> messages = new LinkedList<String>(); 342 343 public RecordingSimpleLog(String name) { 344 super(name); 345 } 346 347 /** 348 * Clear all the recorded log messages. 349 */ 350 public void clearMessages() { 351 messages.clear(); 352 } 353 354 /** 355 * Return true if msg is found in any recorded log messages. 356 * @param msg 357 * @return 358 */ 359 public boolean containsMessage(final String msg) { 360 for (String message : messages) { 361 if (message.contains(msg)) { 362 return true; 363 } 364 } 365 return false; 366 } 367 368 @Override 369 protected boolean isLevelEnabled(int logLevel) { 370 return (logLevel >= LOG_LEVEL_INFO); 371 } 372 373 @Override 374 protected void log(int type, Object message, Throwable t) { 375 super.log(type, message, t); 376 messages.add(message.toString()); 377 } 378 } 379} 380