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 018package org.apache.commons.scxml2.env.javascript; 019 020import java.util.Collection; 021import java.util.HashSet; 022import java.util.Map; 023import java.util.Set; 024 025import javax.script.Bindings; 026import javax.script.SimpleBindings; 027 028import org.apache.commons.scxml2.Context; 029 030/** 031 * Wrapper class for the JDK Javascript engine Bindings class that extends the 032 * wrapped Bindings to search the SCXML context for variables and predefined 033 * functions that do not exist in the wrapped Bindings. 034 * 035 */ 036public class JSBindings implements Bindings { 037 038 private static final String NASHORN_GLOBAL = "nashorn.global"; 039 040 // INSTANCE VARIABLES 041 042 private Bindings bindings; 043 private Context context; 044 045 // CONSTRUCTORS 046 047 /** 048 * Initialises the internal Bindings delegate and SCXML context. 049 * 050 * @param context SCXML Context to use for script variables. 051 * @param bindings Javascript engine bindings for Javascript variables. 052 * 053 * @throws IllegalArgumentException Thrown if either <code>context</code> 054 * or <code>bindings</code> is <code>null</code>. 055 * 056 */ 057 public JSBindings(Context context, Bindings bindings) { 058 // ... validate 059 060 if (context == null) { 061 throw new IllegalArgumentException("Invalid SCXML context"); 062 } 063 064 if (bindings == null) { 065 throw new IllegalArgumentException("Invalid script Bindings"); 066 } 067 068 // ... initialise 069 070 this.bindings = bindings; 071 this.context = context; 072 } 073 074 // INSTANCE METHODS 075 076 /** 077 * Returns <code>true</code> if the wrapped Bindings delegate 078 * or SCXML context contains a variable identified by 079 * <code>key</code>. 080 * 081 */ 082 @Override 083 public boolean containsKey(Object key) { 084 if (hasGlobalBindings() && getGlobalBindings().containsKey(key)) { 085 return true; 086 } 087 088 if (bindings.containsKey(key)) { 089 return true; 090 } 091 092 return context.has(key.toString()); 093 } 094 095 /** 096 * Returns a union of the wrapped Bindings entry set and the 097 * SCXML context entry set. 098 * <p> 099 * NOTE: doesn't seem to be invoked ever. Not thread-safe. 100 * 101 */ 102 @Override 103 public Set<String> keySet() { 104 Set<String> keys = new HashSet<String>(); 105 106 keys.addAll(context.getVars().keySet()); 107 keys.addAll(bindings.keySet()); 108 109 if (hasGlobalBindings()) { 110 keys.addAll(getGlobalBindings().keySet()); 111 } 112 113 return keys; 114 } 115 116 /** 117 * Returns the combined size of the wrapped Bindings entry set and the 118 * SCXML context entry set. 119 * <p> 120 * NOTE: doesn't seem to be invoked ever so not sure if it works in 121 * context. Not thread-safe. 122 * 123 */ 124 @Override 125 public int size() { 126 Set<String> keys = new HashSet<String>(); 127 128 keys.addAll(context.getVars().keySet()); 129 keys.addAll(bindings.keySet()); 130 131 if (hasGlobalBindings()) { 132 keys.addAll(getGlobalBindings().keySet()); 133 } 134 135 return keys.size(); 136 } 137 138 /** 139 * Returns <code>true</code> if the wrapped Bindings delegate 140 * or SCXML context contains <code>value</code>. 141 * <p> 142 * NOTE: doesn't seem to be invoked ever so not sure if it works in 143 * context. Not thread-safe. 144 */ 145 @Override 146 public boolean containsValue(Object value) { 147 if (hasGlobalBindings() && getGlobalBindings().containsValue(value)) { 148 return true; 149 } 150 151 if (bindings.containsValue(value)) { 152 return true; 153 } 154 155 return context.getVars().containsValue(value); 156 } 157 158 /** 159 * Returns a union of the wrapped Bindings entry set and the 160 * SCXML context entry set. 161 * <p> 162 * NOTE: doesn't seem to be invoked ever so not sure if it works in 163 * context. Not thread-safe. 164 */ 165 @Override 166 public Set<Map.Entry<String,Object>> entrySet() { 167 return union().entrySet(); 168 } 169 170 /** 171 * Returns a union of the wrapped Bindings value list and the 172 * SCXML context value list. 173 * <p> 174 * NOTE: doesn't seem to be invoked ever so not sure if it works in 175 * context. Not thread-safe. 176 */ 177 @Override 178 public Collection<Object> values() { 179 return union().values(); 180 } 181 182 /** 183 * Returns a <code>true</code> if both the Bindings delegate and 184 * the SCXML context maps are empty. 185 * <p> 186 * NOTE: doesn't seem to be invoked ever so not sure if it works in 187 * context. Not thread-safe. 188 */ 189 @Override 190 public boolean isEmpty() { 191 if (hasGlobalBindings() && !getGlobalBindings().isEmpty()) { 192 return false; 193 } 194 195 if (!bindings.isEmpty()) { 196 return false; 197 } 198 199 return context.getVars().isEmpty(); 200 } 201 202 /** 203 * Returns the value from the wrapped Bindings delegate 204 * or SCXML context contains identified by <code>key</code>. 205 * 206 */ 207 @Override 208 public Object get(Object key) { 209 // nashorn.global should be retrieved from the bindings, not from context. 210 if (NASHORN_GLOBAL.equals(key)) { 211 return bindings.get(key); 212 } 213 214 if (hasGlobalBindings() && getGlobalBindings().containsKey(key)) { 215 return getGlobalBindings().get(key); 216 } 217 218 if (bindings.containsKey(key)) { 219 return bindings.get(key); 220 } 221 222 return context.get(key.toString()); 223 } 224 225 /** 226 * The following delegation model is used to set values: 227 * <ol> 228 * <li>Delegates to {@link Context#set(String,Object)} if the 229 * {@link Context} contains the key (name), else</li> 230 * <li>Delegates to the wrapped {@link Bindings#put(String, Object)} 231 * if the {@link Bindings} contains the key (name), else</li> 232 * <li>Delegates to {@link Context#setLocal(String, Object)}</li> 233 * </ol> 234 * 235 */ 236 @Override 237 public Object put(String name, Object value) { 238 Object old = context.get(name); 239 240 // nashorn.global should be put into the bindings, not into context. 241 if (NASHORN_GLOBAL.equals(name)) { 242 return bindings.put(name, value); 243 } else if (context.has(name)) { 244 context.set(name, value); 245 } else if (bindings.containsKey(name)) { 246 return bindings.put(name, value); 247 } else if (hasGlobalBindings() && getGlobalBindings().containsKey(name)) { 248 return getGlobalBindings().put(name, value); 249 } else { 250 context.setLocal(name, value); 251 } 252 253 return old; 254 } 255 256 /** 257 * Delegates to the wrapped Bindings <code>putAll</code> method i.e. does 258 * not store variables in the SCXML context. 259 * <p> 260 * NOTE: doesn't seem to be invoked ever so not sure if it works in 261 * context. Not thread-safe. 262 */ 263 @Override 264 public void putAll(Map<? extends String, ? extends Object> toMerge) { 265 bindings.putAll(toMerge); 266 } 267 268 /** 269 * Removes the object from the wrapped Bindings instance or the contained 270 * SCXML context. Not entirely sure about this implementation but it 271 * follows the philosophy of using the Javascript Bindings as a child context 272 * of the SCXML context. 273 * <p> 274 * NOTE: doesn't seem to be invoked ever so not sure if it works in 275 * context. Not thread-safe. 276 */ 277 @Override 278 public Object remove(Object key) { 279 if (hasGlobalBindings() && getGlobalBindings().containsKey(key)) { 280 getGlobalBindings().remove(key); 281 } 282 283 if (bindings.containsKey(key)) { 284 return bindings.remove(key); 285 } 286 287 if (context.has(key.toString())) { 288 return context.getVars().remove(key); 289 } 290 291 return Boolean.FALSE; 292 } 293 294 /** 295 * Delegates to the wrapped Bindings <code>clear</code> method. Does not clear 296 * the SCXML context. 297 * <p> 298 * NOTE: doesn't seem to be invoked ever so not sure if it works in 299 * context. Not thread-safe. 300 */ 301 @Override 302 public void clear() { 303 bindings.clear(); 304 } 305 306 /** 307 * Internal method to create a union of the SCXML context and the Javascript 308 * Bindings. Does a heavyweight copy - and so far only invoked by the 309 * not used methods. 310 */ 311 private Bindings union() { 312 Bindings set = new SimpleBindings(); 313 314 set.putAll(context.getVars()); 315 316 for (String key : bindings.keySet()) { 317 set.put(key, bindings.get(key)); 318 } 319 320 if (hasGlobalBindings()) { 321 for (String key : getGlobalBindings().keySet()) { 322 set.put(key, getGlobalBindings().get(key)); 323 } 324 } 325 326 return set; 327 } 328 329 /** 330 * Return true if a global bindings (i.e. nashorn Global instance) was ever set by the script engine. 331 * <p> 332 * Note: because the global binding can be set by the script engine when evaluating a script, we should 333 * check or retrieve the global binding whenever needed instead of initialization time. 334 * </p> 335 * @return true if a global bindings (i.e. nashorn Global instance) was ever set by the script engine 336 */ 337 protected boolean hasGlobalBindings() { 338 if (bindings.containsKey(NASHORN_GLOBAL)) { 339 return true; 340 } 341 342 return false; 343 } 344 345 /** 346 * Return the global bindings (i.e. nashorn Global instance) set by the script engine if existing. 347 * @return the global bindings (i.e. nashorn Global instance) set by the script engine, or null if not existing. 348 */ 349 protected Bindings getGlobalBindings() { 350 if (bindings.containsKey(NASHORN_GLOBAL)) { 351 return (Bindings) bindings.get(NASHORN_GLOBAL); 352 } 353 354 return null; 355 } 356}