/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.script;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import org.apache.commons.io.FilenameUtils;
import org.geoserver.config.GeoServerDataDirectory;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.resource.Files;
import org.geoserver.platform.resource.Paths;
import org.geoserver.platform.resource.Resource;
import org.geoserver.platform.resource.Resource.Type;
import org.geoserver.platform.resource.Resources;
import org.geoserver.script.app.AppHook;
import org.geoserver.script.function.FunctionHook;
import org.geoserver.script.wfs.WfsTxHook;
import org.geoserver.script.wps.WpsHook;
import org.geoserver.security.GeoServerSecurityManager;
import org.geotools.util.logging.Logging;
import org.springframework.beans.factory.InitializingBean;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
/**
* Facade for the scripting subsystem, providing methods for obtaining script context and managing
* scripts.
*
* @author Justin Deoliveira, OpenGeo
*
*/
public class ScriptManager implements InitializingBean {
static Logger LOGGER = Logging.getLogger(ScriptManager.class);
GeoServerDataDirectory dataDir;
ScriptEngineManager engineMgr;
GeoServerSecurityManager secMgr;
volatile List<ScriptPlugin> plugins;
Cache<Long, ScriptSession> sessions;
public ScriptManager(GeoServerDataDirectory dataDir) {
this.dataDir = dataDir;
engineMgr = new ScriptEngineManager();
sessions = CacheBuilder.newBuilder()
.maximumSize(10).expireAfterAccess(10, TimeUnit.MINUTES).build();
}
public GeoServerDataDirectory getDataDirectory() {
return dataDir;
}
public GeoServerSecurityManager getSecurityManager() {
return secMgr != null ? secMgr :GeoServerExtensions.bean(GeoServerSecurityManager.class);
}
public void setSecurityManager(GeoServerSecurityManager secMgr) {
this.secMgr = secMgr;
}
/**
* Returns the underlying engine manager used to create and manage script engines.
*/
public ScriptEngineManager getEngineManager() {
return engineMgr;
}
/**
* Returns list of available script plugins.
*/
public List<ScriptPlugin> getPlugins() {
return plugins();
}
/**
* The root "scripts" directory, located directly under the root of the data directory.
*
*/
public Resource script() {
return dataDir.get("scripts");
}
/**
* Gets a script directory located at the specified path
*
*/
public Resource script(String... path) {
return script().get(Paths.path(path));
}
/**
* The root "scripts" directory, located directly under the root of the data directory.
*
*/
public Resource app() {
return dataDir.get("scripts/apps");
}
/**
* The root "scripts" directory, located directly under the root of the data directory.
*
*/
public Resource app(String path) {
return app().get(path);
}
/**
* The root "scripts" directory, located directly under the root of the data directory.
*
* @deprecated use {@link #script()}
*/
@Deprecated
public File getScriptRoot() throws IOException {
return script().dir();
}
/**
* Finds a script directory located at the specified path, returning <code>null</code> if no
* such directory exists.
*
* @deprecated use {@link #script(String)}
*/
@Deprecated
public File findScriptDir(String path) throws IOException {
Resource r = script(path);
if (r.getType() == Type.RESOURCE) {
return r.dir();
}
return null;
}
/**
* Finds a script directory located at the specified path, creating the directory if it does not
* already exist.
*
* @deprecated use {@link #script(String...)}
*
*/
@Deprecated
public File findOrCreateScriptDir(String path) {
return script(path).dir();
}
/**
* Finds a script file with the specified filename located in the specified directory path,
* returning <code>null</code> if the file does not exist.
*
* @deprecated use {@link #script(String...)}
*/
@Deprecated
public File findScriptFile(String dirPath, String filename) throws IOException {
Resource res = script(dirPath + File.separator + filename);
return Resources.exists(res) ? res.file() : null;
}
/**
* Finds a script file at the specified path, returning <code>null</code> if the file does not
* exist.
*
* @deprecated use {@link #script(String...)}
*/
@Deprecated
public File findScriptFile(String path) throws IOException {
Resource res = script(path);
return Resources.exists(res) ? res.file() : null;
}
/**
* Finds a script file at the specified path, creating it if necessary.
*
* @deprecated use {@link #script(String...)}
*/
@Deprecated
public File findOrCreateScriptFile(String path) throws IOException {
return script(path).file();
}
/**
* The root "apps" directory, located directly under {@link #getScriptRoot()}.
*
* @deprecated use {@link #app()}
*/
@Deprecated
public File getAppRoot() throws IOException {
return app().dir();
}
/**
* Finds a named app dir, returning <code>null</code> if the directory does not exist.
*
* @deprecated use {@link #app(String)}
*/
@Deprecated
public File findAppDir(String app) throws IOException {
return app(app).dir();
}
/**
* Finds a named app dir, creating if it does not already exist.
*
* @deprecated use {@link #app(String)}
*/
@Deprecated
public File findOrCreateAppDir(String app) throws IOException {
return app(app).dir();
}
/**
* Find the main script File
*/
public Resource findAppMainScript(Resource appDir) {
Resource main = null;
if (appDir != null) {
for (Resource f : appDir.list()) {
if ("main".equals(FilenameUtils.getBaseName(f.name()))) {
main = f;
break;
}
}
}
return main;
}
/**
* Find the main script File
*
* @deprecated use {@link #findAppMainScript(Resource)}
*/
@Deprecated
public File findAppMainScript(File appDir) {
return findAppMainScript(Files.asResource(appDir)).file();
}
/**
* The root "wps" directory, located directly under {@link #script()}
*/
public Resource wps() throws IOException {
return script("wps");
}
/**
* The root "wfs/tx" directory, located directly under {@link #script()}
*/
public Resource wfsTx() throws IOException {
return script("scripts", "wfs", "tx");
}
/**
* The root "function" directory, located directly under {@link #script()}
*/
public Resource function() {
return script("function");
}
public Resource lib(String ext) throws IOException {
return script("lib").get(ext);
}
/**
* The root "wps" directory, located directly under {@link #getScriptRoot()}
*
* @deprecated use {@link #wps()}
*/
@Deprecated
public File getWpsRoot() throws IOException {
return wps().dir();
}
/**
* The root "wfs/tx" directory, located directly under {@link #getScriptRoot()}
*
* @deprecated use {@link #wfsTx()}
*/
@Deprecated
public File getWfsTxRoot() throws IOException {
return wfsTx().dir();
}
/**
* The root "function" directory, located directly under {@link #getScriptRoot()}
*
* @deprecated use {@link #function()}
*/
@Deprecated
public File getFunctionRoot() throws IOException {
return function().dir();
}
/**
* @deprecated use {@link #lib(String)}
*/
@Deprecated
public File getLibRoot(String ext) throws IOException {
return script("lib/" + ext).dir();
}
/**
* Creates a new script engine for the specified script file.
*/
public ScriptEngine createNewEngine(Resource script) {
return createNewEngine(ext(script.name()));
}
/**
* Creates a new script engine for the specified script file.
*/
@Deprecated
public ScriptEngine createNewEngine(File script) {
return createNewEngine(ext(script.getName()));
}
/**
* Creates a new script engine for the specified file extension.
*/
public ScriptEngine createNewEngine(String ext) {
if (ext == null) {
return null;
}
return initEngine(engineMgr.getEngineByExtension(ext));
}
public ScriptSession createNewSession(String ext) {
ScriptSession session = new ScriptSession(createNewEngine(ext), ext);
sessions.put(session.getId(), session);
return session;
}
public ScriptSession findSession(long id) {
return sessions.getIfPresent(id);
}
public List<ScriptSession> findSessions(String ext) {
List<ScriptSession> sids = new ArrayList<ScriptSession>();
for (Map.Entry<Long, ScriptSession> e : sessions.asMap().entrySet()) {
if (ext != null && !ext.equalsIgnoreCase(e.getValue().getExtension())) {
continue;
}
sids.add(e.getValue());
}
return sids;
}
/*
* Initializes a new script engine by looking up the plugin matching the engines factory.
*/
ScriptEngine initEngine(ScriptEngine engine) {
if (engine == null) {
return null;
}
for (ScriptPlugin plugin : plugins()) {
if (plugin.getScriptEngineFactoryClass().isInstance(engine.getFactory())) {
plugin.initScriptEngine(engine);
break;
}
}
return engine;
}
/**
* Looks up the app hook for the specified script returning <code>null</code> if no such
* hook can be found.
* <p>
* This method works by looking up all {@link ScriptPlugin} instances and delegating to
* {@link ScriptPlugin#createAppHook()}.
* </p>
*/
public AppHook lookupAppHook(Resource script) {
ScriptPlugin p = plugin(script.name());
return p != null ? p.createAppHook() : new AppHook(null);
}
/**
* Looks up the wps hook for the specified script returning <code>null</code> if no such
* hook can be found.
* <p>
* This method works by looking up all {@link ScriptPlugin} instances and delegating to
* {@link ScriptPlugin#createWpsHook()}.
* </p>
*/
public WpsHook lookupWpsHook(Resource script) {
ScriptPlugin p = plugin(script.name());
return p != null ? p.createWpsHook() : null;
}
/**
* Looks up the filter hook for the specified script returning <code>null</code> if no such
* hook can be found.
* <p>
* This method works by looking up all {@link ScriptPlugin} instances and delegating to
* {@link ScriptPlugin#createFunctionHook()}.
* </p>
*/
public FunctionHook lookupFilterHook(Resource script) {
ScriptPlugin p = plugin(script.name());
return p != null ? p.createFunctionHook() : new FunctionHook(null);
}
/**
* Looks up the wfs tx hook for the specified script returning <code>null</code> if no such
* hook can be found.
* <p>
* This method works by looking up all {@link ScriptPlugin} instances and delegating to
* {@link ScriptPlugin#createWfsTxHook()}.
* </p>
*/
public WfsTxHook lookupWfsTxHook(Resource script) {
ScriptPlugin p = plugin(script.name());
return p != null ? p.createWfsTxHook() : new WfsTxHook(null);
}
public String lookupPluginId(Resource script) {
ScriptPlugin p = plugin(script.name());
return p != null ? p.getId() : null;
}
public String lookupPluginDisplayName(Resource script) {
ScriptPlugin p = plugin(script.name());
return p != null ? p.getDisplayName() : null;
}
public String lookupPluginEditorMode(Resource script) {
ScriptPlugin p = plugin(script.name());
return p != null ? p.getEditorMode() : null;
}
public boolean hasEngineForExtension(Resource ext) {
for (ScriptEngineFactory f : engineMgr.getEngineFactories()) {
if (f.getExtensions().contains(ext)) {
return true;
}
}
return false;
}
/**
* Looks up the app hook for the specified script returning <code>null</code> if no such
* hook can be found.
* <p>
* This method works by looking up all {@link ScriptPlugin} instances and delegating to
* {@link ScriptPlugin#createAppHook()}.
* </p>
*/
@Deprecated
public AppHook lookupAppHook(File script) {
ScriptPlugin p = plugin(script.getName());
return p != null ? p.createAppHook() : new AppHook(null);
}
/**
* Looks up the wps hook for the specified script returning <code>null</code> if no such
* hook can be found.
* <p>
* This method works by looking up all {@link ScriptPlugin} instances and delegating to
* {@link ScriptPlugin#createWpsHook()}.
* </p>
*/
@Deprecated
public WpsHook lookupWpsHook(File script) {
ScriptPlugin p = plugin(script.getName());
return p != null ? p.createWpsHook() : null;
}
/**
* Looks up the filter hook for the specified script returning <code>null</code> if no such
* hook can be found.
* <p>
* This method works by looking up all {@link ScriptPlugin} instances and delegating to
* {@link ScriptPlugin#createFunctionHook()}.
* </p>
*/
@Deprecated
public FunctionHook lookupFilterHook(File script) {
ScriptPlugin p = plugin(script.getName());
return p != null ? p.createFunctionHook() : new FunctionHook(null);
}
/**
* Looks up the wfs tx hook for the specified script returning <code>null</code> if no such
* hook can be found.
* <p>
* This method works by looking up all {@link ScriptPlugin} instances and delegating to
* {@link ScriptPlugin#createWfsTxHook()}.
* </p>
*/
@Deprecated
public WfsTxHook lookupWfsTxHook(File script) {
ScriptPlugin p = plugin(script.getName());
return p != null ? p.createWfsTxHook() : new WfsTxHook(null);
}
@Deprecated
public String lookupPluginId(File script) {
ScriptPlugin p = plugin(script.getName());
return p != null ? p.getId() : null;
}
@Deprecated
public String lookupPluginDisplayName(File script) {
ScriptPlugin p = plugin(script.getName());
return p != null ? p.getDisplayName() : null;
}
@Deprecated
public String lookupPluginEditorMode(File script) {
ScriptPlugin p = plugin(script.getName());
return p != null ? p.getEditorMode() : null;
}
public boolean hasEngineForExtension(String ext) {
for (ScriptEngineFactory f : engineMgr.getEngineFactories()) {
if (f.getExtensions().contains(ext)) {
return true;
}
}
return false;
}
/*
* Looks up all {@link ScriptPlugin} instances in the application context.
*/
List<ScriptPlugin> plugins() {
if (plugins == null) {
synchronized (this) {
if (plugins == null) {
plugins = GeoServerExtensions.extensions(ScriptPlugin.class);
for (ScriptPlugin plugin : plugins) {
try {
plugin.init(this);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error initializing plugin", e);
}
}
}
}
}
return plugins;
}
/*
* Looks up the plugin for the specified script.
*/
ScriptPlugin plugin(String scriptName) {
String ext = ext(scriptName);
for (ScriptPlugin plugin : plugins()) {
if (ext.equalsIgnoreCase(plugin.getExtension())) {
return plugin;
}
}
return null;
}
/*
* Helper method for extracting extension from filename throwing exception if the file has no
* extension.
*/
String ext(String scriptName) throws IllegalArgumentException {
String ext = FilenameUtils.getExtension(scriptName);
if (ext == null) {
throw new IllegalArgumentException(scriptName + " has no extension");
}
return ext;
}
/**
* Find the File based on the name, ScriptType and extension. The File and it's parent directories
* do not have to exist, they will be created.
* @param name The name of the script
* @param type The ScriptType (wps, function wfstx, app)
* @param extension The extension (js, py, groovy)
* @return The script File
*/
public Resource scriptFile(String name, ScriptType type, String extension) throws IOException {
Resource dir = null;
if (type == ScriptType.WPS) {
dir = this.wps();
} else if (type == ScriptType.FUNCTION) {
dir = this.function();
} else if (type == ScriptType.WFSTX) {
dir = this.wfsTx();
} else if (type == ScriptType.APP) {
dir = this.app();
}
if (type == ScriptType.APP) {
Resource appDir = dir.get(name);
return appDir.get("main." + extension);
} else {
return dir.get(name + "." + extension);
}
}
/**
* Find the File based on the name, ScriptType and extension. The File and it's parent directories
* do not have to exist, they will be created.
* @param name The name of the script
* @param type The ScriptType (wps, function wfstx, app)
* @param extension The extension (js, py, groovy)
* @return The script File
*
* @Depecrated (use {@link #scriptFile()})
*/
@Deprecated
public File findScriptFile(String name, ScriptType type, String extension) throws IOException {
return scriptFile(name, type, extension).file();
}
/**
* Determine the ScriptType for the File
* @param file The File
* @return The ScriptType
*/
public ScriptType getScriptType(Resource file) {
Resource dir = file.parent();
if (dir.name().equals("function")) {
return ScriptType.FUNCTION;
} else if (dir.name().equals("tx") && dir.parent().name().equals("wfs")) {
return ScriptType.WFSTX;
} else if (dir.name().equals("wps") || dir.parent().name().equals("wps")) {
return ScriptType.WPS;
} else if (dir.parent().name().equals("apps")) {
return ScriptType.APP;
} else {
throw new IllegalArgumentException("Can't determine ScriptType for " + file + "'!");
}
}
/**
* Determine the ScriptType for the File
* @param file The File
* @return The ScriptType
*/
@Deprecated
public ScriptType getScriptType(File file) {
File dir = file.getParentFile();
if (dir.getName().equals("function")) {
return ScriptType.FUNCTION;
} else if (dir.getName().equals("tx") && dir.getParentFile().getName().equals("wfs")) {
return ScriptType.WFSTX;
} else if (dir.getName().equals("wps") || dir.getParentFile().getName().equals("wps")) {
return ScriptType.WPS;
} else if (dir.getParentFile().getName().equals("apps")) {
return ScriptType.APP;
} else {
throw new IllegalArgumentException("Can't determine ScriptType for " + file + "'!");
}
}
/**
* Look up the editor mode by File extension
* @param ext The file extension (js, groovy, py)
* @return The codemirror editor mode or null
*/
public String lookupEditorModeByExtension(String ext) {
ScriptPlugin p = null;
for (ScriptPlugin plugin : plugins()) {
if (ext.equalsIgnoreCase(plugin.getExtension())) {
p = plugin;
}
}
if (p != null) {
return p.getEditorMode();
} else {
return null;
}
}
@Override
public void afterPropertiesSet() throws Exception {
plugins();
}
}