    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.model;
    018
    019    import java.util.Collection;
    020
    021    import javax.xml.parsers.DocumentBuilderFactory;
    022
    023    import org.apache.commons.logging.Log;
    024    import org.apache.commons.logging.LogFactory;
    025    import org.apache.commons.scxml.Context;
    026    import org.apache.commons.scxml.ErrorReporter;
    027    import org.apache.commons.scxml.Evaluator;
    028    import org.apache.commons.scxml.EventDispatcher;
    029    import org.apache.commons.scxml.PathResolver;
    030    import org.apache.commons.scxml.SCInstance;
    031    import org.apache.commons.scxml.SCXMLExpressionException;
    032    import org.apache.commons.scxml.SCXMLHelper;
    033    import org.apache.commons.scxml.TriggerEvent;
    034    import org.apache.commons.scxml.semantics.ErrorConstants;
    035    import org.w3c.dom.Document;
    036    import org.w3c.dom.Node;
    037
    038    /**
    039     * The class in this SCXML object model that corresponds to the
    040     * <assign> SCXML element.
    041     *
    042     */
    043    public class Assign extends Action implements PathResolverHolder {
    044
    045        /**
    046         * Serial version UID.
    047         */
    048        private static final long serialVersionUID = 1L;
    049
    050        /**
    051         * Left hand side expression evaluating to a previously
    052         * defined variable.
    053         */
    054        private String name;
    055
    056        /**
    057         * Left hand side expression evaluating to a location within
    058         * a previously defined XML data tree.
    059         */
    060        private String location;
    061
    062        /**
    063         * The source where the new XML instance for this location exists.
    064         */
    065        private String src;
    066
    067        /**
    068         * Expression evaluating to the new value of the variable.
    069         */
    070        private String expr;
    071
    072        /**
    073         * {@link PathResolver} for resolving the "src" result.
    074         */
    075        private PathResolver pathResolver;
    076
    077        /**
    078         * Constructor.
    079         */
    080        public Assign() {
    081            super();
    082        }
    083
    084        /**
    085         * Get the variable to be assigned a new value.
    086         *
    087         * @return Returns the name.
    088         */
    089        public String getName() {
    090            return name;
    091        }
    092
    093        /**
    094         * Get the variable to be assigned a new value.
    095         *
    096         * @param name The name to set.
    097         */
    098        public void setName(final String name) {
    099            this.name = name;
    100        }
    101
    102        /**
    103         * Get the expr that will evaluate to the new value.
    104         *
    105         * @return Returns the expr.
    106         */
    107        public String getExpr() {
    108            return expr;
    109        }
    110
    111        /**
    112         * Set the expr that will evaluate to the new value.
    113         *
    114         * @param expr The expr to set.
    115         */
    116        public void setExpr(final String expr) {
    117            this.expr = expr;
    118        }
    119
    120        /**
    121         * Get the location for a previously defined XML data tree.
    122         *
    123         * @return Returns the location.
    124         */
    125        public String getLocation() {
    126            return location;
    127        }
    128
    129        /**
    130         * Set the location for a previously defined XML data tree.
    131         *
    132         * @param location The location.
    133         */
    134        public void setLocation(final String location) {
    135            this.location = location;
    136        }
    137
    138        /**
    139         * Get the source where the new XML instance for this location exists.
    140         *
    141         * @return Returns the source.
    142         */
    143        public String getSrc() {
    144            return src;
    145        }
    146
    147        /**
    148         * Set the source where the new XML instance for this location exists.
    149         *
    150         * @param src The source.
    151         */
    152        public void setSrc(final String src) {
    153            this.src = src;
    154        }
    155
    156        /**
    157         * Get the {@link PathResolver}.
    158         *
    159         * @return Returns the pathResolver.
    160         */
    161        public PathResolver getPathResolver() {
    162            return pathResolver;
    163        }
    164
    165        /**
    166         * Set the {@link PathResolver}.
    167         *
    168         * @param pathResolver The pathResolver to set.
    169         */
    170        public void setPathResolver(final PathResolver pathResolver) {
    171            this.pathResolver = pathResolver;
    172        }
    173
    174        /**
    175         * {@inheritDoc}
    176         */
    177        public void execute(final EventDispatcher evtDispatcher,
    178                final ErrorReporter errRep, final SCInstance scInstance,
    179                final Log appLog, final Collection derivedEvents)
    180        throws ModelException, SCXMLExpressionException {
    181            TransitionTarget parentTarget = getParentTransitionTarget();
    182            Context ctx = scInstance.getContext(parentTarget);
    183            Evaluator eval = scInstance.getEvaluator();
    184            ctx.setLocal(getNamespacesKey(), getNamespaces());
    185            // "location" gets preference over "name"
    186            if (!SCXMLHelper.isStringEmpty(location)) {
    187                Node oldNode = eval.evalLocation(ctx, location);
    188                if (oldNode != null) {
    189                    //// rvalue may be ...
    190                    // a Node, if so, import it at location
    191                    Node newNode = null;
    192                    try {
    193                        if (src != null && src.trim().length() > 0) {
    194                            newNode = getSrcNode();
    195                        } else {
    196                            newNode = eval.evalLocation(ctx, expr);
    197                        }
    198                        // Remove all children
    199                        Node removeChild = oldNode.getFirstChild();
    200                        while (removeChild != null) {
    201                            Node nextChild = removeChild.getNextSibling();
    202                            oldNode.removeChild(removeChild);
    203                            removeChild = nextChild;
    204                        }
    205                        if (newNode != null) {
    206                            // Adopt new children
    207                            for (Node child = newNode.getFirstChild();
    208                                    child != null;
    209                                    child = child.getNextSibling()) {
    210                                Node importedNode = oldNode.getOwnerDocument().
    211                                    importNode(child, true);
    212                                oldNode.appendChild(importedNode);
    213                            }
    214                        }
    215                    } catch (SCXMLExpressionException see) {
    216                        // or something else, stuff toString() into lvalue
    217                        Object valueObject = eval.eval(ctx, expr);
    218                        SCXMLHelper.setNodeValue(oldNode, valueObject.toString());
    219                    }
    220                    if (appLog.isDebugEnabled()) {
    221                        appLog.debug("<assign>: data node '" + oldNode.getNodeName()
    222                            + "' updated");
    223                    }
    224                    TriggerEvent ev = new TriggerEvent(name + ".change",
    225                        TriggerEvent.CHANGE_EVENT);
    226                    derivedEvents.add(ev);
    227                } else {
    228                    appLog.error("<assign>: location does not point to"
    229                        + " a <data> node");
    230                }
    231            } else {
    232                // lets try "name" (usage as in Sep '05 WD, useful with <var>)
    233                if (!ctx.has(name)) {
    234                    errRep.onError(ErrorConstants.UNDEFINED_VARIABLE, name
    235                        + " = null", parentTarget);
    236                } else {
    237                    Object varObj = null;
    238                    if (src != null && src.trim().length() > 0) {
    239                        varObj = getSrcNode();
    240                    } else {
    241                        varObj = eval.eval(ctx, expr);
    242                    }
    243                    ctx.set(name, varObj);
    244                    if (appLog.isDebugEnabled()) {
    245                        appLog.debug("<assign>: Set variable '" + name + "' to '"
    246                            + String.valueOf(varObj) + "'");
    247                    }
    248                    TriggerEvent ev = new TriggerEvent(name + ".change",
    249                        TriggerEvent.CHANGE_EVENT);
    250                    derivedEvents.add(ev);
    251                }
    252            }
    253            ctx.setLocal(getNamespacesKey(), null);
    254        }
    255
    256        /**
    257         * Get the {@link Node} the "src" attribute points to.
    258         *
    259         * @return The node the "src" attribute points to.
    260         */
    261        private Node getSrcNode() {
    262            String resolvedSrc = src;
    263            if (pathResolver != null) {
    264                resolvedSrc = pathResolver.resolvePath(src);
    265            }
    266            Document doc = null;
    267            try {
    268                doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().
    269                    parse(resolvedSrc);
    270            } catch (Throwable t) {
    271                org.apache.commons.logging.Log log = LogFactory.
    272                    getLog(Assign.class);
    273                log.error(t.getMessage(), t);
    274            }
    275            if (doc == null) {
    276                return null;
    277            }
    278            return doc.getDocumentElement();
    279        }
    280
    281    }