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    }