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 }