001 /* 002 * Copyright 2002-2008 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016 package org.springframework.richclient.script; 017 018 import java.awt.BorderLayout; 019 import java.io.IOException; 020 import java.io.InputStreamReader; 021 import java.util.HashMap; 022 import java.util.Map; 023 024 import javax.script.Bindings; 025 import javax.script.ScriptContext; 026 import javax.script.ScriptEngine; 027 import javax.script.ScriptEngineManager; 028 import javax.script.ScriptException; 029 import javax.script.SimpleScriptContext; 030 import javax.swing.JComponent; 031 import javax.swing.JPanel; 032 033 import org.springframework.beans.factory.InitializingBean; 034 import org.springframework.core.io.Resource; 035 import org.springframework.richclient.application.View; 036 import org.springframework.richclient.application.support.AbstractView; 037 import org.springframework.util.Assert; 038 import org.springframework.util.StringUtils; 039 040 /** 041 * A {@link View} implementation that uses {@link ScriptEngine} to build its control. 042 * 043 * @author Peter De Bruycker 044 */ 045 public class ScriptedView extends AbstractView implements InitializingBean { 046 private Resource script; 047 private String engineName; 048 private Map<String, Object> scriptBindings; 049 private String viewBindingName; 050 private String containerBindingName; 051 052 protected JComponent createControl() { 053 JPanel container = new JPanel(new BorderLayout()); 054 055 ScriptEngine engine = createScriptEngine(); 056 057 Bindings bindings = engine.createBindings(); 058 populateBindings(bindings, container); 059 060 ScriptContext context = new SimpleScriptContext(); 061 // Bug workaround 062 context.setBindings(bindings, ScriptContext.GLOBAL_SCOPE); 063 context.setBindings(bindings, ScriptContext.ENGINE_SCOPE); 064 065 engine.setContext(context); 066 067 try { 068 engine.eval(new InputStreamReader(script.getInputStream())); 069 } 070 catch (ScriptException e) { 071 throw new ScriptExecutionException("error running script", e); 072 } 073 catch (IOException e) { 074 throw new ScriptIOException("error reading script", e); 075 } 076 077 return container; 078 } 079 080 /** 081 * Creates the <code>ScriptEngine</code>, by using the {@link #engineName} if provided. If no engine name is set, 082 * the extension of the file name of the {@link #script} is used. 083 * 084 * @return the <code>ScriptEngine</code> 085 * 086 * @see ScriptEngineManager#getEngineByName(String) 087 * @see ScriptEngineManager#getEngineByExtension(String) 088 */ 089 protected ScriptEngine createScriptEngine() { 090 ScriptEngineManager manager = new ScriptEngineManager(getClass().getClassLoader()); 091 092 if (StringUtils.hasText(engineName)) { 093 return manager.getEngineByName(engineName); 094 } 095 096 return manager.getEngineByExtension(getExtension(script.getFilename())); 097 } 098 099 private String getExtension(String fileName) { 100 return fileName.substring(fileName.lastIndexOf('.') + 1); 101 } 102 103 /** 104 * Populates the bindings that will be passed to the script. 105 * <p> 106 * If the {@link #containerBindingName} is set, the container instance will be included in the bindings. 107 * <p> 108 * If the {@link #viewBindingName} is set, the view instance will be included in the bindings. 109 * <p> 110 * All the variables in the {@link #scriptBindings} will also be included in the bindings. 111 * 112 * @param bindings 113 * the bindings 114 * @param container 115 * the compontent that will be passed into the script 116 * 117 * @see #setContainerBindingName(String) 118 * @see #setViewBindingName(String) 119 * @see #setScriptBindings(Map) 120 */ 121 protected void populateBindings(Bindings bindings, JComponent container) { 122 if (StringUtils.hasText(containerBindingName)) { 123 bindings.put(containerBindingName, container); 124 } 125 if (StringUtils.hasText(viewBindingName)) { 126 bindings.put(viewBindingName, this); 127 } 128 129 if (this.scriptBindings != null) { 130 for (String key : scriptBindings.keySet()) { 131 bindings.put(key, scriptBindings.get((key))); 132 } 133 } 134 } 135 136 public void setScript(Resource script) { 137 this.script = script; 138 } 139 140 /** 141 * Sets the name of the engine to be created. This name will be used to create the engine. 142 * 143 * @param name 144 * the name 145 * 146 * @see ScriptEngineManager#getEngineByName(String) 147 */ 148 public void setEngineName(String name) { 149 engineName = name; 150 } 151 152 /** 153 * Set the bindings to be passed to the script. 154 * 155 * @param bindings 156 * the bindings 157 */ 158 public void setScriptBindings(Map<String, Object> bindings) { 159 // be nice and take a copy 160 this.scriptBindings = new HashMap<String, Object>(bindings); 161 } 162 163 /** 164 * Sets the view binding name. 165 * 166 * @param viewBindingName 167 * the name 168 * @see #populateBindings(Bindings, JComponent) for more details 169 */ 170 public void setViewBindingName(String viewBindingName) { 171 this.viewBindingName = viewBindingName; 172 } 173 174 /** 175 * Sets the container binding name. 176 * 177 * @param containerBindingName 178 * the name 179 * @see #populateBindings(Bindings, JComponent) for more details 180 */ 181 public void setContainerBindingName(String containerBindingName) { 182 this.containerBindingName = containerBindingName; 183 } 184 185 public void afterPropertiesSet() throws Exception { 186 Assert.notNull(script, "script must be set"); 187 } 188 }