/*
* Copyright (c) 2013-2014 Red Hat, Inc. and/or its affiliates.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Cheng Fang - Initial API and implementation
*/
package org.jberet.job.model;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URI;
import java.net.URL;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import org.jberet._private.BatchLogger;
import org.jberet._private.BatchMessages;
/**
* Represents a script element under an enclosing element represented by {@link org.jberet.job.model.RefArtifact}.
* The script may be included inline in job XML as element text or CDATA, or reference an external resource via
* {@code src} attribute. For example,
* <p/>
* <pre>
*<step id="batchletJavascriptSrc.step1">
* <batchlet>
* <script src="javascript/simple-batchlet.js"/>
* </batchlet>
*</step>
* </pre>
*<pre>
*<step id="batchletJavascriptInline.step1">
*<batchlet>
*<script type="javascript">
* function stop() {
* print('In stop function\n');
* }
*
* //access built-in variables: jobContext, stepContext and batchProperties,
* //set job exit status to the value of testName property, and
* //return the value of testName property as step exit status,
*
* function process() {
* print('jobName: ' + jobContext.getJobName() + '\n');
* print('stepName: ' + stepContext.getStepName() + '\n');
* var testName = batchProperties.get('testName');
* jobContext.setExitStatus(testName);
* return testName;
* }
* </script>
* </batchlet>
*</step>
* </pre>
*/
public class Script implements Serializable, Cloneable {
private static final long serialVersionUID = 3048730979495066553L;
private static volatile ScriptEngineManager scriptEngineManager;
String type;
String src;
String content;
Script(final String type, final String src) {
this.type = type;
this.src = src;
}
/**
* Gets the content of the script as string.
* If the script is specified as inline in job XML, its content is returned. Otherwise, this method tries to load
* the content of the resource as specified in {@code scr attribute} fo the {@code script} element in job XML.
*
* @param classLoader class loader to load the resource
* @return the string content of the script
*/
public String getContent(final ClassLoader classLoader) {
if (content != null) {
return content;
}
InputStream is = classLoader.getResourceAsStream(src);
if (is == null) {
BatchLogger.LOGGER.tracef("script src %s is not loadable by ClassLoader, next try file", src);
try {
final File file = new File(src);
if (file.exists() && file.isFile()) {
is = new FileInputStream(file);
}
} catch (final Exception e) {
BatchLogger.LOGGER.tracef(e, "exception when loading script src %s as a file, next try URL", src);
}
if (is == null) {
try {
final URL url = new URI(src).toURL();
is = url.openStream();
} catch (final Exception e) {
BatchLogger.LOGGER.tracef(e, "exception when loading script src %s as URL", src);
}
}
}
if (is == null) {
throw BatchMessages.MESSAGES.failToGetScriptContent(null, src);
}
java.util.Scanner scanner = null;
try {
scanner = new java.util.Scanner(is).useDelimiter("\\A");
return scanner.hasNext() ? scanner.next() : "";
} finally {
if (scanner != null) {
try {
//closing the scannr also closes the InputStream source.
scanner.close();
} catch (final Exception e) {
BatchLogger.LOGGER.tracef(e, "Failed to close resource %s", src);
}
}
}
}
/**
* Gets the type of the script, for example, {@code javascript}.
* @return type of the script
*/
public String getType() {
return type;
}
/**
* Gets the javax.script.ScriptEngine for this script.
*
* @param classLoader class loader used to obtain the script engine
* @return {@code ScriptEngine} for this script
*/
public ScriptEngine getEngine(final ClassLoader classLoader) {
ScriptEngineManager mgr = scriptEngineManager;
if (mgr == null) {
synchronized (Script.class) {
mgr = scriptEngineManager;
if (mgr == null) {
scriptEngineManager = mgr = new ScriptEngineManager(classLoader);
}
}
}
ScriptEngine engine = null;
if (type != null) {
engine = type.indexOf('/') > 0 ? mgr.getEngineByMimeType(type) : mgr.getEngineByName(type);
} else if (src != null) {
final int fileExtensionDot = src.lastIndexOf('.');
if (fileExtensionDot < 0) {
throw BatchMessages.MESSAGES.invalidScriptAttributes(type, src);
}
final String ext = src.substring(fileExtensionDot + 1);
engine = mgr.getEngineByExtension(ext);
}
if (engine != null) {
if (engine.getClass().getSimpleName().indexOf("Groovy") > -1) {
engine.getContext().setAttribute("#jsr223.groovy.engine.keep.globals", "weak", ScriptContext.ENGINE_SCOPE);
}
return engine;
}
throw BatchMessages.MESSAGES.invalidScriptAttributes(type, src);
}
@Override
protected Script clone() {
final Script c = new Script(this.type, this.src);
c.content = this.content;
return c;
}
}