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 }