/* * Copyright (C) 2005-2012 BetaCONCEPT Limited * * This file is part of Astroboa. * * Astroboa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Astroboa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Astroboa. If not, see <http://www.gnu.org/licenses/>. */ package org.betaconceptframework.astroboa.console.scripting; import groovy.lang.Binding; import groovy.lang.GroovyClassLoader; import groovy.lang.GroovyObject; import groovy.util.GroovyScriptEngine; import groovy.util.ResourceException; import groovy.util.ScriptException; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.naming.InitialContext; import org.betaconceptframework.astroboa.api.model.ContentObject; import org.betaconceptframework.astroboa.api.model.StringProperty; import org.betaconceptframework.astroboa.api.model.io.ResourceRepresentationType; import org.betaconceptframework.astroboa.api.model.query.CmsOutcome; import org.betaconceptframework.astroboa.api.model.query.criteria.ContentObjectCriteria; import org.betaconceptframework.astroboa.client.AstroboaClient; import org.betaconceptframework.astroboa.console.jsf.PageController; import org.betaconceptframework.astroboa.context.JcrContext; import org.betaconceptframework.astroboa.model.factory.CmsCriteriaFactory; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Factory; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Out; import org.jboss.seam.annotations.Scope; import org.jboss.seam.contexts.Contexts; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; /** * @author Gregory Chomatas (gchomatas@betaconcept.com) * @author Savvas Triantafyllou (striantafyllou@betaconcept.com) * Created on Jun 28, 2009 */ @Name("scriptEngine") @Scope(ScopeType.PAGE) public class ScriptEngine { private final Logger logger = LoggerFactory.getLogger(getClass()); private AstroboaClient astroboaClient; private PageController pageController; private String SCRIPTS_HOME_DIR = "astroboa-scripts"; // holds utility functions exposed to running script private ScriptEngineUtilityAPI utilityAPI = new ScriptEngineUtilityAPI(); @In(required=false) @Out(required=false) private String scriptSourceCode; @Out(required=false) private File loadedScriptFile; @In(required=false) @Out(required=false) private String currentScriptFileName; private JcrContext astroboaJcrContext; public void addNewScript() { currentScriptFileName="myNewScript.groovy"; loadedScriptFile = null; scriptSourceCode = null; pageController.loadPageComponentInDynamicUIArea("/WEB-INF/pageComponents/scriptEngine/scriptEngine.xhtml"); } public void loadScriptFromFileSystem(File scriptFile) { if (scriptFile.canRead()) { StringBuilder scriptContent = new StringBuilder(); try { FileInputStream fis = new FileInputStream(scriptFile); InputStreamReader in = new InputStreamReader(fis, "UTF-8"); BufferedReader input = new BufferedReader(in); try { String line = null; /* * readLine is a bit quirky : * it returns the content of a line MINUS the newline. * it returns null only for the END of the stream. * it returns an empty String if two newlines appear in a row. */ while (( line = input.readLine()) != null){ scriptContent.append(line); scriptContent.append(System.getProperty("line.separator")); } } finally { input.close(); } } catch (IOException e){ logger.warn("Script content cannot be loaded.", e); utilityAPI.appendWarningToConsole("Script content cannot be loaded. IOException was thrown while loading. Message is:\n" + e.getMessage() + "\n" + prepareStackTrace(e)); } scriptSourceCode = scriptContent.toString(); loadedScriptFile = scriptFile; currentScriptFileName = scriptFile.getName(); pageController.loadPageComponentInDynamicUIArea("/WEB-INF/pageComponents/scriptEngine/scriptEngine.xhtml"); } } public String getConfirmationMessageForScriptSave() { File scriptFile = new File(System.getProperty("jboss.server.config.dir").substring(5) + SCRIPTS_HOME_DIR + File.separator + currentScriptFileName ); if (scriptFile.exists()) { return "The file already exists. Are you sure you want to update it? If not press cancel and choose another file name."; } else { return "A new file will be created with the name:" + currentScriptFileName; } } public void saveScriptToFileSystem() { File scriptFile = new File(System.getProperty("jboss.server.config.dir").substring(5) + SCRIPTS_HOME_DIR + File.separator + currentScriptFileName ); try { FileOutputStream fos = new FileOutputStream(scriptFile); OutputStreamWriter out = new OutputStreamWriter(fos, "UTF-8"); BufferedWriter outWriter = new BufferedWriter(out); try { outWriter.write(scriptSourceCode); utilityAPI.appendMessageToConsole( "Script has been saved."); // reread the script list Contexts.getPageContext().set("availableScripts", getAvailableScripts()); // the currently loaded file should be the saved file loadedScriptFile = scriptFile; } finally { outWriter.close(); } } catch (FileNotFoundException fileNotFoundException) { utilityAPI.appendWarningToConsole( "The requested file for saving cannot be found"); } catch (UnsupportedEncodingException unsupportedEncodingException) { utilityAPI.appendWarningToConsole( "The requested file encoding (UTF-8) is not supported"); } catch (IOException e) { utilityAPI.appendWarningToConsole("IOException was thrown while saving the script. Message is:\n" + e.getMessage() + "\n" + prepareStackTrace(e)); } } @Factory(value="availableScripts", scope=ScopeType.PAGE) public List<File> getAvailableScripts() { List<File> scriptFileList = new ArrayList<File>(); if (System.getProperty("jboss.server.config.dir") != null){ File scriptHomeDir = new File(System.getProperty("jboss.server.config.dir").substring(5) + SCRIPTS_HOME_DIR + File.separator); if (scriptHomeDir.isDirectory()) { FileFilter groovySriptFileFilter = new FileFilter() { @Override public boolean accept(File file) { return file.getName().endsWith("groovy"); } }; scriptFileList = Arrays.asList(scriptHomeDir.listFiles(groovySriptFileFilter)); } } return scriptFileList; } // The script will be retrieved from a specific file path inside the jboss conf directory. public void runScriptWithScriptEngine(String scriptFileName) { // reset console buffer utilityAPI.emptyConsoleBuffer(); Binding binding; // The 'binding' makes instances of the application objects available as 'variables' in the script URL[] roots; // A list of directories to search for Groovy scripts (think of it as a PATH). URI scriptsAbsolutePath = null; if (System.getProperty("jboss.server.config.dir") != null){ //We expect to find the scripts in JBOSS-HOME/server/default/conf/astroboa-scripts directory try { scriptsAbsolutePath = new URI(System.getProperty("jboss.server.config.dir")+SCRIPTS_HOME_DIR+File.separator); roots = new URL[]{scriptsAbsolutePath.toURL()}; // The root list is filled with the locations to be searched for the script } catch (URISyntaxException e) { logger.warn("A script path cannot be configured for the script engine. The script engine cannot run"); utilityAPI.appendWarningToConsole("URISyntaxException was thrown in starting Groovy engine. Message is:\n" + e.getMessage() + "\n" + prepareStackTrace(e)); return; } catch (MalformedURLException e) { logger.warn("A script path cannot be configured for the script engine. The script engine cannot run"); utilityAPI.appendWarningToConsole("MalformedURLException was thrown in starting Groovy engine. Message is:\n" + e.getMessage() + "\n" + prepareStackTrace(e)); return; } } else { logger.warn("A script path cannot be configured for the script engine. The script engine cannot run"); utilityAPI.appendWarningToConsole("A script path cannot be configured for the script engine. The script engine cannot run"); return; } Binding scriptenv = new Binding(); // A new Binding is created ... // ... and filled with two 'variables': // an instance of astroboaClient already logged in // to the current repository as the current user scriptenv.setVariable("astroboaClient", astroboaClient); // and an instance of the ScriptEngineUtilityAPI class as a utilities API provider for the running script. scriptenv.setVariable("utilityAPI", utilityAPI); //Expose Jcr context if (astroboaJcrContext == null) { try { InitialContext ctx = new InitialContext(); ApplicationContext springManagedRepositoryServicesContext = (ApplicationContext) ctx.lookup("astroboa.engine.context"); if (springManagedRepositoryServicesContext != null) { astroboaJcrContext = (JcrContext) springManagedRepositoryServicesContext.getBean("jcrContext"); } } catch (Exception e) { e.printStackTrace (); } } scriptenv.setVariable("astroboaJcrContext", astroboaJcrContext); binding = scriptenv; GroovyScriptEngine gse = null; gse = new GroovyScriptEngine(roots); // instantiate the script engine ... if (gse != null) { try { gse.run(scriptFileName, binding); // ... and running the specified script } catch (ResourceException re) { logger.warn("ResourceException in calling groovy script '" + scriptsAbsolutePath.getPath() + scriptFileName, re); utilityAPI.appendWarningToConsole("ResourceException in calling groovy script '" + scriptsAbsolutePath.getPath() + scriptFileName + "' Message is:\n" +re.getMessage() + "\n" + prepareStackTrace(re)); } catch (ScriptException se) { logger.warn("ScriptException in calling groovy script '" + scriptsAbsolutePath.getPath() + scriptFileName, se); utilityAPI.appendWarningToConsole("ScriptException in calling groovy script '" + scriptsAbsolutePath.getPath() + scriptFileName + "' Message is:\n" +se.getMessage() + "\n" + prepareStackTrace(se)); } catch (Exception e) { logger.warn("Exception was thrown in calling groovy script '" + scriptsAbsolutePath.getPath() + scriptFileName, e); utilityAPI.appendWarningToConsole("ScriptException in calling groovy script '" + scriptsAbsolutePath.getPath() + scriptFileName + "' Message is:\n" + e.getMessage() + "\n" + prepareStackTrace(e)); } } } // The script is retrieved from astroboa repository. The scriptObjectName is the system name of a content object of type "scriptObject" which contains the script public void runScriptWithClassLoader(String scriptObjectName) { ContentObject scriptObject = null; ContentObjectCriteria contentObjectCriteria = CmsCriteriaFactory.newContentObjectCriteria("scriptObject"); contentObjectCriteria.addSystemNameEqualsCriterion(scriptObjectName); CmsOutcome<ContentObject> cmsOutcome = astroboaClient.getContentService().searchContentObjects(contentObjectCriteria, ResourceRepresentationType.CONTENT_OBJECT_LIST); if (cmsOutcome.getCount() > 0) { scriptObject = cmsOutcome.getResults().get(0); } else { logger.warn("No Script Object exists with systen name: " + scriptObjectName); utilityAPI.appendWarningToConsole("No Script Object exists with systen name:" + scriptObjectName); return; } if (((StringProperty)scriptObject.getCmsProperty("body")).getSimpleTypeValue() == null) { logger.warn("Script Object: " + scriptObjectName + " contains an empty script"); utilityAPI.appendWarningToConsole("Script Object: " + scriptObjectName + " contains an empty script"); return; } ClassLoader parentClassLoader = getClass().getClassLoader(); GroovyClassLoader groovyClassLoader = new GroovyClassLoader(parentClassLoader); if (groovyClassLoader != null) { Class scriptClass = groovyClassLoader.parseClass(((StringProperty)scriptObject.getCmsProperty("body")).getSimpleTypeValue()); try { GroovyObject groovyObject = (GroovyObject) scriptClass.newInstance(); Object[] args = {}; groovyObject.invokeMethod("run", args); } catch (Exception e) { logger.warn("Script Class could not be created", e); utilityAPI.appendWarningToConsole("Script Class could not be created"); } } else { logger.warn("Script Engine could not be created"); utilityAPI.appendWarningToConsole("Script Engine could not be created"); } } // prepare a stack trace to be shown in an output window private String prepareStackTrace(Exception e) { Throwable exc = e; StringBuffer output = new StringBuffer(); collectTraces(exc, output); if (exc.getCause() != null) { exc = exc.getCause(); output.append("caused by::\n"); output.append(exc.getMessage()); output.append("\n"); collectTraces(exc, output); } return output.toString(); } private void collectTraces(Throwable e, StringBuffer output) { StackTraceElement[] trace = e.getStackTrace(); for (int i=0; i < trace.length; i++) { output.append(trace[i].toString()); output.append("\n"); } } public ScriptEngineUtilityAPI getUtilityAPI() { return utilityAPI; } }