    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