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     */
017    package org.apache.commons.scxml;
018    
019    import java.io.Serializable;
020    import java.util.Iterator;
021    import java.util.Map;
022    import java.util.Set;
023    
024    import javax.xml.transform.TransformerException;
025    
026    import org.apache.commons.logging.Log;
027    import org.apache.commons.logging.LogFactory;
028    import org.apache.commons.scxml.model.TransitionTarget;
029    import org.apache.xml.utils.PrefixResolver;
030    import org.apache.xpath.XPath;
031    import org.apache.xpath.XPathAPI;
032    import org.apache.xpath.XPathContext;
033    import org.w3c.dom.Node;
034    import org.w3c.dom.NodeList;
035    
036    /**
037     * Implementations of builtin functions defined by the SCXML
038     * specification.
039     *
040     * The current version of the specification defines one builtin
041     * predicate In()
042     */
043    public class Builtin implements Serializable {
044    
045        /**
046         * Serial version UID.
047         */
048        private static final long serialVersionUID = 1L;
049    
050        /**
051         * Implements the In() predicate for SCXML documents. The method
052         * name chosen is different since "in" is a reserved token
053         * in some expression languages.
054         *
055         * Does this state belong to the given Set of States.
056         * Simple ID based comparator, assumes IDs are unique.
057         *
058         * @param allStates The Set of State objects to look in
059         * @param state The State ID to compare with
060         * @return Whether this State belongs to this Set
061         */
062        public static boolean isMember(final Set allStates,
063                final String state) {
064            for (Iterator i = allStates.iterator(); i.hasNext();) {
065                TransitionTarget tt = (TransitionTarget) i.next();
066                if (state.equals(tt.getId())) {
067                    return true;
068                }
069            }
070            return false;
071        }
072    
073        /**
074         * Implements the Data() function for Commons SCXML documents, that
075         * can be used to obtain a node from one of the XML data trees.
076         * Manifests within "location" attribute of <assign> element,
077         * for Commons JEXL and Commons EL based documents.
078         *
079         * @param namespaces The current document namespaces map at XPath location
080         * @param data The context Node, though the method accepts an Object
081         *             so error is reported by Commons SCXML, rather
082         *             than the underlying expression language.
083         * @param path The XPath expression.
084         * @return The first node matching the path, or null if no nodes match.
085         */
086        public static Node dataNode(final Map namespaces, final Object data,
087                final String path) {
088            if (data == null || !(data instanceof Node)) {
089                Log log = LogFactory.getLog(Builtin.class);
090                log.error("Data(): Cannot evaluate an XPath expression"
091                    + " in the absence of a context Node, null returned");
092                return null;
093            }
094            Node dataNode = (Node) data;
095            NodeList result = null;
096            try {
097                if (namespaces == null || namespaces.size() == 0) {
098                    Log log = LogFactory.getLog(Builtin.class);
099                    if (log.isDebugEnabled()) {
100                        log.debug("Turning off namespaced XPath evaluation since "
101                            + "no namespace information is available for path: "
102                            + path);
103                    }
104                    result = XPathAPI.selectNodeList(dataNode, path);
105                } else {
106                    XPathContext xpathSupport = new XPathContext();
107                    PrefixResolver prefixResolver =
108                        new DataPrefixResolver(namespaces);
109                    XPath xpath = new XPath(path, null, prefixResolver,
110                        XPath.SELECT);
111                    int ctxtNode = xpathSupport.getDTMHandleFromNode(dataNode);
112                    result = xpath.execute(xpathSupport, ctxtNode,
113                        prefixResolver).nodelist();
114                }
115            } catch (TransformerException te) {
116                Log log = LogFactory.getLog(Builtin.class);
117                log.error(te.getMessage(), te);
118                return null;
119            }
120            int length = result.getLength();
121            if (length == 0) {
122                Log log = LogFactory.getLog(Builtin.class);
123                log.warn("Data(): No nodes matching the XPath expression \""
124                    + path + "\", returning null");
125                return null;
126            } else {
127                if (length > 1) {
128                    Log log = LogFactory.getLog(Builtin.class);
129                    log.warn("Data(): Multiple nodes matching XPath expression \""
130                        + path + "\", returning first");
131                }
132                return result.item(0);
133            }
134        }
135    
136        /**
137         * A variant of the Data() function for Commons SCXML documents,
138         * coerced to a Double, a Long or a String, whichever succeeds,
139         * in that order.
140         * Manifests within rvalue expressions in the document,
141         * for Commons JEXL and Commons EL based documents..
142         *
143         * @param namespaces The current document namespaces map at XPath location
144         * @param data The context Node, though the method accepts an Object
145         *             so error is reported by Commons SCXML, rather
146         *             than the underlying expression language.
147         * @param path The XPath expression.
148         * @return The first node matching the path, coerced to a String, or null
149         *         if no nodes match.
150         */
151        public static Object data(final Map namespaces, final Object data,
152                final String path) {
153            Object retVal = null;
154            String strVal = SCXMLHelper.getNodeValue(dataNode(namespaces,
155                data, path));
156            // try as a double
157            try {
158                double d = Double.parseDouble(strVal);
159                retVal = new Double(d);
160            } catch (NumberFormatException notADouble) {
161                // else as a long
162                try {
163                    long l = Long.parseLong(strVal);
164                    retVal = new Long(l);
165                } catch (NumberFormatException notALong) {
166                    // fallback to string
167                    retVal = strVal;
168                }
169            }
170            return retVal;
171        }
172    
173        /**
174         * Implements the Data() function for Commons SCXML documents, that
175         * can be used to obtain a node from one of the XML data trees.
176         * Manifests within "location" attribute of <assign> element,
177         * for Commons JEXL and Commons EL based documents.
178         *
179         * @param data The context Node, though the method accepts an Object
180         *             so error is reported by Commons SCXML, rather
181         *             than the underlying expression language.
182         * @param path The XPath expression.
183         * @return The first node matching the path, or null if no nodes match.
184         *
185         * @deprecated Use {@link #dataNode(Map,Object,String)} instead
186         */
187        public static Node dataNode(final Object data, final String path) {
188            if (data == null || !(data instanceof Node)) {
189                Log log = LogFactory.getLog(Builtin.class);
190                log.error("Data(): Cannot evaluate an XPath expression"
191                    + " in the absence of a context Node, null returned");
192                return null;
193            }
194            Node dataNode = (Node) data;
195            NodeList result = null;
196            try {
197                result = XPathAPI.selectNodeList(dataNode, path);
198            } catch (TransformerException te) {
199                Log log = LogFactory.getLog(Builtin.class);
200                log.error(te.getMessage(), te);
201                return null;
202            }
203            int length = result.getLength();
204            if (length == 0) {
205                Log log = LogFactory.getLog(Builtin.class);
206                log.warn("Data(): No nodes matching the XPath expression \""
207                    + path + "\", returning null");
208                return null;
209            } else {
210                if (length > 1) {
211                    Log log = LogFactory.getLog(Builtin.class);
212                    log.warn("Data(): Multiple nodes matching XPath expression \""
213                        + path + "\", returning first");
214                }
215                return result.item(0);
216            }
217        }
218    
219        /**
220         * A variant of the Data() function for Commons SCXML documents,
221         * coerced to a Double, a Long or a String, whichever succeeds,
222         * in that order.
223         * Manifests within rvalue expressions in the document,
224         * for Commons JEXL and Commons EL based documents..
225         *
226         * @param data The context Node, though the method accepts an Object
227         *             so error is reported by Commons SCXML, rather
228         *             than the underlying expression language.
229         * @param path The XPath expression.
230         * @return The first node matching the path, coerced to a String, or null
231         *         if no nodes match.
232         *
233         * @deprecated Use {@link #data(Map,Object,String)} instead
234         */
235        public static Object data(final Object data, final String path) {
236            Object retVal = null;
237            String strVal = SCXMLHelper.getNodeValue(dataNode(data, path));
238            // try as a double
239            try {
240                double d = Double.parseDouble(strVal);
241                retVal = new Double(d);
242            } catch (NumberFormatException notADouble) {
243                // else as a long
244                try {
245                    long l = Long.parseLong(strVal);
246                    retVal = new Long(l);
247                } catch (NumberFormatException notALong) {
248                    // fallback to string
249                    retVal = strVal;
250                }
251            }
252            return retVal;
253        }
254    
255        /**
256         * Prefix resolver for XPaths pointing to <data> nodes.
257         */
258        private static class DataPrefixResolver implements PrefixResolver {
259    
260            /** Cached namespaces. */
261            private Map namespaces;
262    
263            /**
264             * Constructor.
265             * @param namespaces The prefix to namespace URI map.
266             */
267            private DataPrefixResolver(final Map namespaces) {
268                this.namespaces = namespaces;
269            }
270    
271            /** {@inheritDoc} */
272            public String getNamespaceForPrefix(final String prefix) {
273                return (String) namespaces.get(prefix);
274            }
275    
276            /** {@inheritDoc} */
277            public String getNamespaceForPrefix(final String prefix,
278                    final Node nsContext) {
279                return (String) namespaces.get(prefix);
280            }
281    
282            /** {@inheritDoc} */
283            public String getBaseIdentifier() {
284                return null;
285            }
286    
287            /** {@inheritDoc} */
288            public boolean handlesNullPrefixes() {
289                return false;
290            }
291    
292        }
293    
294    }
295