View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.scxml2.io;
18  
19  import java.io.IOException;
20  import java.io.StringReader;
21  import java.util.ArrayList;
22  import java.util.LinkedList;
23  import java.util.List;
24  
25  import javax.xml.stream.XMLStreamException;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogConfigurationException;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.commons.logging.impl.LogFactoryImpl;
31  import org.apache.commons.logging.impl.SimpleLog;
32  import org.apache.commons.scxml2.ActionExecutionContext;
33  import org.apache.commons.scxml2.SCXMLExpressionException;
34  import org.apache.commons.scxml2.SCXMLTestHelper;
35  import org.apache.commons.scxml2.io.SCXMLReader.Configuration;
36  import org.apache.commons.scxml2.model.Action;
37  import org.apache.commons.scxml2.model.CustomAction;
38  import org.apache.commons.scxml2.model.Data;
39  import org.apache.commons.scxml2.model.Datamodel;
40  import org.apache.commons.scxml2.model.EnterableState;
41  import org.apache.commons.scxml2.model.ExternalContent;
42  import org.apache.commons.scxml2.model.Final;
43  import org.apache.commons.scxml2.model.ModelException;
44  import org.apache.commons.scxml2.model.SCXML;
45  import org.apache.commons.scxml2.model.Send;
46  import org.apache.commons.scxml2.model.State;
47  import org.apache.commons.scxml2.model.Transition;
48  import org.junit.After;
49  import org.junit.AfterClass;
50  import org.junit.Assert;
51  import org.junit.Before;
52  import org.junit.BeforeClass;
53  import org.junit.Test;
54  import org.w3c.dom.Node;
55  
56  /**
57   * Unit tests {@link org.apache.commons.scxml2.io.SCXMLReader}.
58   */
59  public class SCXMLReaderTest {
60  
61      private static String oldLogFactoryProperty;
62  
63      private Log scxmlReaderLog;
64  
65      @BeforeClass
66      public static void beforeClass() {
67          oldLogFactoryProperty = System.getProperty(LogFactory.FACTORY_PROPERTY);
68          System.setProperty(LogFactory.FACTORY_PROPERTY, RecordingLogFactory.class.getName());
69      }
70  
71      @AfterClass
72      public static void afterClass() {
73          if (oldLogFactoryProperty == null) {
74              System.clearProperty(LogFactory.FACTORY_PROPERTY);
75          } else {
76              System.setProperty(LogFactory.FACTORY_PROPERTY, oldLogFactoryProperty);
77          }
78      }
79  
80      /**
81       * Set up instance variables required by this test case.
82       */
83      @Before
84      public void before() {
85          scxmlReaderLog = LogFactory.getLog(SCXMLReader.class);
86          clearRecordedLogMessages();
87      }
88  
89      /**
90       * Test the implementation
91       */    
92      @Test
93      public void testSCXMLReaderMicrowave03Sample() throws Exception {
94          SCXML scxml = SCXMLTestHelper.parse("org/apache/commons/scxml2/env/jexl/microwave-03.xml");
95          Assert.assertNotNull(scxml);
96          Assert.assertNotNull(serialize(scxml));
97      }
98      
99      @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