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