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 }