001: /*
002: * Copyright 2006-2007 The Scriptella Project Team.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of 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,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package scriptella.driver.script;
017:
018: import scriptella.configuration.ConfigurationException;
019: import scriptella.expression.LineIterator;
020: import scriptella.spi.AbstractConnection;
021: import scriptella.spi.ConnectionParameters;
022: import scriptella.spi.DialectIdentifier;
023: import scriptella.spi.ParametersCallback;
024: import scriptella.spi.ProviderException;
025: import scriptella.spi.QueryCallback;
026: import scriptella.spi.Resource;
027: import scriptella.util.ExceptionUtils;
028: import scriptella.util.IOUtils;
029: import scriptella.util.StringUtils;
030:
031: import javax.script.Compilable;
032: import javax.script.CompiledScript;
033: import javax.script.ScriptContext;
034: import javax.script.ScriptEngine;
035: import javax.script.ScriptEngineFactory;
036: import javax.script.ScriptEngineManager;
037: import javax.script.ScriptException;
038: import java.io.IOException;
039: import java.io.PrintWriter;
040: import java.io.Reader;
041: import java.io.Writer;
042: import java.net.URL;
043: import java.util.ArrayList;
044: import java.util.IdentityHashMap;
045: import java.util.List;
046: import java.util.Map;
047: import java.util.logging.Logger;
048:
049: /**
050: * Scriptella connection adapter for the JSR 223: Scripting for the Java™ Platform.
051: * <p>For configuration details and examples see <a href="package-summary.html">overview page</a>.
052: * <p/>
053: *
054: * @author Fyodor Kupolov
055: * @version 1.0
056: */
057: public class ScriptConnection extends AbstractConnection {
058: private static final Logger LOG = Logger
059: .getLogger(ScriptConnection.class.getName());
060: private Map<Resource, CompiledScript> cache;
061: private ScriptEngine engine;
062: private Compilable compiler;
063: private String encoding;
064: private URL url;
065: private Writer out;
066: /**
067: * Name of the <code>language</code> connection property.
068: */
069: static final String LANGUAGE = "language";
070:
071: /**
072: * Name of the <code>encoding</code> connection property.
073: * Specifies charset encoding of a character stream specified by an url connection parameter.
074: */
075: static final String ENCODING = "encoding";
076:
077: /**
078: * Instantiates a new connection to JSR 223 scripting engine.
079: *
080: * @param parameters connection parameters.
081: */
082: public ScriptConnection(ConnectionParameters parameters) {
083: super (parameters);
084: String lang = parameters.getStringProperty(LANGUAGE);
085: ScriptEngineManager scriptEngineManager = new ScriptEngineManager(
086: ScriptConnection.class.getClassLoader());
087: if (StringUtils.isEmpty(lang)) { //JavaScript is used by default
088: LOG
089: .fine("Script language was not specified. JavaScript is default.");
090: lang = "js";
091: }
092:
093: engine = scriptEngineManager.getEngineByName(lang);
094: if (engine == null) {
095: throw new ConfigurationException("Specified " + LANGUAGE
096: + "=" + lang
097: + " not supported. Available values are: "
098: + getAvailableEngines(scriptEngineManager));
099: }
100: if (engine instanceof Compilable) {
101: compiler = (Compilable) engine;
102: cache = new IdentityHashMap<Resource, CompiledScript>();
103: } else {
104: LOG
105: .info("Engine "
106: + engine.getFactory().getEngineName()
107: + " does not support compilation. Running in interpreted mode.");
108: }
109: if (!StringUtils.isEmpty(parameters.getUrl())) { //if url is specified
110: url = parameters.getResolvedUrl();
111: //setup reader and writer for it
112: ScriptContext ctx = engine.getContext();
113: ctx.setReader(new LazyReader());
114: //JS engine bug - we have to wrap with PrintWriter, because otherwise print function won't work.
115: ctx.setWriter(new PrintWriter(new LazyWriter()));
116: }
117: encoding = parameters.getCharsetProperty(ENCODING);
118: ScriptEngineFactory f = engine.getFactory();
119: setDialectIdentifier(new DialectIdentifier(f.getLanguageName(),
120: f.getLanguageVersion()));
121:
122: }
123:
124: /**
125: * Returns available script engine names including all aliases.
126: *
127: * @param manager script manager instance to use.
128: * @return list of languages each of them represented by a list of names..
129: */
130: static List<List<String>> getAvailableEngines(
131: ScriptEngineManager manager) {
132: List<List<String>> list = new ArrayList<List<String>>();
133: for (ScriptEngineFactory scriptEngineFactory : manager
134: .getEngineFactories()) {
135: list.add(scriptEngineFactory.getNames());
136: }
137: return list;
138: }
139:
140: public void executeScript(Resource scriptContent,
141: ParametersCallback parametersCallback)
142: throws ScriptProviderException, ConfigurationException {
143: run(scriptContent, new BindingsParametersCallback(
144: parametersCallback));
145: }
146:
147: public void executeQuery(Resource queryContent,
148: ParametersCallback parametersCallback,
149: QueryCallback queryCallback)
150: throws ScriptProviderException, ConfigurationException {
151: run(queryContent, new BindingsParametersCallback(
152: parametersCallback, queryCallback));
153: }
154:
155: /**
156: * Compiles and runs the specified resource.
157: *
158: * @param resource resource to compile.
159: * @param parametersCallback parameters callback.
160: */
161: private void run(Resource resource,
162: BindingsParametersCallback parametersCallback) {
163: try {
164: if (compiler == null) {
165: engine.eval(resource.open(), parametersCallback);
166: return;
167: }
168: CompiledScript script = cache.get(resource);
169: if (script == null) {
170: try {
171: cache.put(resource, script = compiler
172: .compile(resource.open()));
173: } catch (ScriptException e) {
174: throw new ScriptProviderException(
175: "Failed to compile script", e,
176: getErrorStatement(resource, e));
177: }
178: }
179: script.eval(parametersCallback);
180: } catch (IOException e) {
181: throw new ScriptProviderException(
182: "Failed to open script for reading", e);
183: } catch (ScriptException e) {
184: throw new ScriptProviderException(
185: "Failed to execute script", e, getErrorStatement(
186: resource, e));
187: }
188:
189: }
190:
191: static String getErrorStatement(Resource resource,
192: ScriptException exception) {
193: LineIterator it = null;
194: try {
195: it = new LineIterator(resource.open());
196: return it.getLineAt(exception.getLineNumber() - 1);
197: } catch (IOException e) {
198: ExceptionUtils.ignoreThrowable(e);
199: } finally {
200: IOUtils.closeSilently(it);
201: }
202: return null;
203: }
204:
205: /**
206: * Closes the connection and releases all related resources.
207: */
208: public void close() throws ProviderException {
209: cache = null;
210: }
211:
212: /**
213: * Lazily initialized reader.
214: */
215: final class LazyReader extends Reader {
216: private Reader r;
217:
218: public int read(char cbuf[], int off, int len)
219: throws IOException {
220: if (r == null) {
221: r = IOUtils.getReader(url.openStream(), encoding);
222: }
223: return r.read(cbuf, off, len);
224: }
225:
226: public void close() throws IOException {
227: if (r != null) {
228: r.close();
229: }
230: }
231: }
232:
233: /**
234: * Lazily initialized writer which appends chars to an URL stream.
235: */
236: final class LazyWriter extends Writer {
237:
238: public void write(char cbuf[], int off, int len)
239: throws IOException {
240: if (out == null) {
241: out = IOUtils.getWriter(IOUtils.getOutputStream(url),
242: encoding);
243: }
244: out.write(cbuf, off, len);
245: }
246:
247: public void flush() throws IOException {
248: if (out != null) {
249: out.flush();
250: }
251: }
252:
253: public void close() throws IOException {
254: if (out != null) {
255: out.close();
256: }
257:
258: }
259: }
260:
261: }
|