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    }