1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.scxml2.env.xpath;
18
19 import java.io.Serializable;
20 import java.util.ArrayList;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map;
24
25 import org.apache.commons.jxpath.ClassFunctions;
26 import org.apache.commons.jxpath.FunctionLibrary;
27 import org.apache.commons.jxpath.Functions;
28 import org.apache.commons.jxpath.JXPathContext;
29 import org.apache.commons.jxpath.JXPathException;
30 import org.apache.commons.jxpath.PackageFunctions;
31 import org.apache.commons.jxpath.ri.model.NodePointer;
32 import org.apache.commons.jxpath.ri.model.VariablePointer;
33 import org.apache.commons.scxml2.Context;
34 import org.apache.commons.scxml2.Evaluator;
35 import org.apache.commons.scxml2.EvaluatorProvider;
36 import org.apache.commons.scxml2.SCXMLExpressionException;
37 import org.apache.commons.scxml2.env.EffectiveContextMap;
38 import org.apache.commons.scxml2.model.SCXML;
39 import org.w3c.dom.Attr;
40 import org.w3c.dom.CharacterData;
41 import org.w3c.dom.Element;
42 import org.w3c.dom.Node;
43 import org.w3c.dom.NodeList;
44
45
46
47
48
49
50
51 public class XPathEvaluator implements Evaluator, Serializable {
52
53
54 private static final long serialVersionUID = -3578920670869493294L;
55
56 public static final String SUPPORTED_DATA_MODEL = Evaluator.XPATH_DATA_MODEL;
57
58
59
60
61 private static class NodePointerList extends ArrayList<NodePointer> {
62 }
63
64 public static class XPathEvaluatorProvider implements EvaluatorProvider {
65
66 @Override
67 public String getSupportedDatamodel() {
68 return SUPPORTED_DATA_MODEL;
69 }
70
71 @Override
72 public Evaluator getEvaluator() {
73 return new XPathEvaluator();
74 }
75
76 @Override
77 public Evaluator getEvaluator(final SCXML document) {
78 return new XPathEvaluator();
79 }
80 }
81
82 private static final JXPathContext jxpathRootContext = JXPathContext.newContext(null);
83
84 static {
85 FunctionLibrary xpathFunctions = new FunctionLibrary();
86 xpathFunctions.addFunctions(new ClassFunctions(XPathFunctions.class, null));
87
88 xpathFunctions.addFunctions(new PackageFunctions("", null));
89 jxpathRootContext.setFunctions(xpathFunctions);
90 }
91
92 private JXPathContext jxpathContext;
93
94
95
96
97 public XPathEvaluator() {
98 jxpathContext = jxpathRootContext;
99 }
100
101
102
103
104
105
106 public XPathEvaluator(final Functions functions) {
107 jxpathContext = JXPathContext.newContext(jxpathRootContext, null);
108 jxpathContext.setFunctions(functions);
109 }
110
111 @Override
112 public String getSupportedDatamodel() {
113 return SUPPORTED_DATA_MODEL;
114 }
115
116
117
118
119 @Override
120 public Object eval(final Context ctx, final String expr)
121 throws SCXMLExpressionException {
122 try {
123 List list = getContext(ctx).selectNodes(expr);
124 if (list.isEmpty()) {
125 return null;
126 }
127 else if (list.size() == 1) {
128 return list.get(0);
129 }
130 return list;
131 } catch (JXPathException xee) {
132 throw new SCXMLExpressionException(xee.getMessage(), xee);
133 }
134 }
135
136
137
138
139 @Override
140 public Boolean evalCond(final Context ctx, final String expr)
141 throws SCXMLExpressionException {
142 try {
143 return (Boolean)getContext(ctx).getValue(expr, Boolean.class);
144 } catch (JXPathException xee) {
145 throw new SCXMLExpressionException(xee.getMessage(), xee);
146 }
147 }
148
149
150
151
152 @Override
153 public Object evalLocation(final Context ctx, final String expr) throws SCXMLExpressionException {
154 JXPathContext context = getContext(ctx);
155 try {
156 Iterator iterator = context.iteratePointers(expr);
157 Object pointer;
158 NodePointerList pointerList = null;
159 while (iterator.hasNext()) {
160 pointer = iterator.next();
161 if (pointer != null && pointer instanceof NodePointer && ((NodePointer)pointer).getNode() != null) {
162 if (pointerList == null) {
163 pointerList = new NodePointerList();
164 }
165 pointerList.add((NodePointer)pointer);
166 }
167 }
168 return pointerList;
169 } catch (JXPathException xee) {
170 throw new SCXMLExpressionException(xee.getMessage(), xee);
171 }
172 }
173
174
175
176
177 public void evalAssign(final Context ctx, final String location, final Object data, final AssignType type,
178 final String attr) throws SCXMLExpressionException {
179
180 Object loc = evalLocation(ctx, location);
181 if (isXPathLocation(ctx, loc)) {
182 assign(ctx, loc, data, type, attr);
183 }
184 else {
185 throw new SCXMLExpressionException("evalAssign - cannot resolve location: '" + location + "'");
186 }
187 }
188
189
190
191
192 public Object evalScript(Context ctx, String script)
193 throws SCXMLExpressionException {
194 throw new UnsupportedOperationException("Scripts are not supported by the XPathEvaluator");
195 }
196
197
198
199
200 @Override
201 public Context newContext(final Context parent) {
202 return new XPathContext(parent);
203 }
204
205
206
207
208
209
210
211 @SuppressWarnings("unused")
212 public boolean isXPathLocation(final Context ctx, Object data) {
213 return data instanceof NodePointerList;
214 }
215
216
217
218
219
220
221
222
223
224
225
226
227 public void assign(final Context ctx, final Object location, final Object data, final AssignType type,
228 final String attr) throws SCXMLExpressionException {
229 if (!isXPathLocation(ctx, location)) {
230 throw new SCXMLExpressionException("assign requires a NodePointerList as location but is of type: " +
231 (location==null ? "(null)" : location.getClass().getName()));
232 }
233 for (NodePointer pointer : (NodePointerList)location) {
234 Object node = pointer.getNode();
235 if (node != null) {
236 if (node instanceof Node) {
237 assign(ctx, (Node)node, pointer.asPath(), data, type != null ? type : AssignType.REPLACE_CHILDREN, attr);
238 }
239 else if (pointer instanceof VariablePointer) {
240 if (type == AssignType.DELETE) {
241 pointer.remove();
242 }
243 VariablePointer vp = (VariablePointer)pointer;
244 Object variable = vp.getNode();
245 if (variable instanceof Node) {
246 assign(ctx, (Node)variable, pointer.asPath(), data, type != null ? type : AssignType.REPLACE_CHILDREN, attr);
247 }
248 else if (type == null || type == AssignType.REPLACE) {
249 String variableName = vp.getName().getName();
250 if (data instanceof CharacterData) {
251 ctx.set(variableName, ((CharacterData)data).getNodeValue());
252 }
253 else {
254 ctx.set(variableName, data);
255 }
256 }
257 else {
258 throw new SCXMLExpressionException("Unsupported assign type +" +
259 type.name()+" for XPath variable "+pointer.asPath());
260 }
261 }
262 else {
263 throw new SCXMLExpressionException("Unsupported XPath location pointer " +
264 pointer.getClass().getName()+" for location "+pointer.asPath());
265 }
266 }
267
268 }
269 }
270
271 @SuppressWarnings("unused")
272 protected void assign(final Context ctx, final Node node, final String nodePath, final Object data,
273 final AssignType type, final String attr) throws SCXMLExpressionException {
274
275 if (type == AssignType.DELETE) {
276 node.getParentNode().removeChild(node);
277 }
278 else if (node instanceof Element) {
279 Element element = (Element)node;
280 if (type == AssignType.ADD_ATTRIBUTE) {
281 if (attr == null) {
282 throw new SCXMLExpressionException("Missing required attribute name for adding attribute at " +
283 nodePath);
284 }
285 if (data == null) {
286 throw new SCXMLExpressionException("Missing required data value for adding attribute " +
287 attr + " to location " + nodePath);
288 }
289 element.setAttribute(attr, data.toString());
290 }
291 else {
292 Node dataNode = null;
293 if (type != AssignType.REPLACE_CHILDREN) {
294 if (data == null) {
295 throw new SCXMLExpressionException("Missing required data value for assign type "+type.name());
296 }
297 dataNode = data instanceof Node
298 ? element.getOwnerDocument().importNode((Node)data, true)
299 : element.getOwnerDocument().createTextNode(data.toString());
300 }
301 switch (type) {
302 case REPLACE_CHILDREN:
303
304 element.setTextContent(null);
305 if (data instanceof Node) {
306 element.appendChild(element.getOwnerDocument().importNode((Node)data, true));
307 }
308 else if (data instanceof List) {
309 for (Object dataElement : (List)data) {
310 if (dataElement instanceof Node) {
311 element.appendChild(element.getOwnerDocument().importNode((Node)dataElement, true));
312 }
313 else if (dataElement != null) {
314 element.appendChild(element.getOwnerDocument().createTextNode(dataElement.toString()));
315 }
316 }
317 }
318 else if (data instanceof NodeList) {
319 NodeList list = (NodeList)data;
320 for (int i = 0, size = list.getLength(); i < size; i++)
321 element.appendChild(element.getOwnerDocument().importNode(list.item(i), true));
322 }
323 else {
324 element.appendChild(element.getOwnerDocument().createTextNode(data.toString()));
325 }
326
327 break;
328 case FIRST_CHILD:
329 element.insertBefore(dataNode, element.getFirstChild());
330 break;
331 case LAST_CHILD:
332 element.appendChild(dataNode);
333 break;
334 case PREVIOUS_SIBLING:
335 element.getParentNode().insertBefore(dataNode, element);
336 break;
337 case NEXT_SIBLING:
338 element.getParentNode().insertBefore(dataNode, element.getNextSibling());
339 break;
340 case REPLACE:
341 element.getParentNode().replaceChild(dataNode, element);
342 break;
343 }
344 }
345 }
346 else if (node instanceof CharacterData) {
347 if (type != AssignType.REPLACE) {
348 throw new SCXMLExpressionException("Assign type "+ type.name() +
349 " not supported for character data node at " + nodePath);
350 }
351 ((CharacterData)node).setData(data.toString());
352 }
353 else if (node instanceof Attr) {
354 if (type != AssignType.REPLACE) {
355 throw new SCXMLExpressionException("Assign type "+ type.name() +
356 " not supported for node attribute at " + nodePath);
357 }
358 ((Attr)node).setValue(data.toString());
359 }
360 else {
361 throw new SCXMLExpressionException("Unsupported assign location Node type "+node.getNodeType());
362 }
363 }
364
365
366 @SuppressWarnings("unchecked")
367 protected JXPathContext getContext(final Context ctx) throws SCXMLExpressionException {
368 JXPathContext context = JXPathContext.newContext(jxpathContext, new EffectiveContextMap(ctx));
369 context.setVariables(new ContextVariables(ctx));
370 Map<String, String> namespaces = (Map<String, String>) ctx.get(Context.NAMESPACES_KEY);
371 if (namespaces != null) {
372 for (String prefix : namespaces.keySet()) {
373 context.registerNamespace(prefix, namespaces.get(prefix));
374 }
375 }
376 return context;
377 }
378 }