Commit 1dea75f5 authored by Matija Obreza's avatar Matija Obreza

Beanshell admin tool

parent cfd309b6
......@@ -537,6 +537,11 @@
<artifactId>glis-client-resttemplate</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache-extras.beanshell</groupId>
<artifactId>bsh</artifactId>
<version>2.0b6</version>
</dependency>
</dependencies>
<build>
......
/*
* Copyright 2017 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.genesys2.server.servlet.controller.admin;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.genesys.glis.v1.api.GenesysApi;
import org.genesys2.server.service.GenesysFilterService;
import org.genesys2.server.service.GenesysService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import bsh.ConsoleInterface;
import bsh.EvalError;
import bsh.Interpreter;
/**
* Beanshell
*
* @author Matija Obreza
*/
@Controller
@RequestMapping("/admin/bsh")
@PreAuthorize("hasRole('ADMINISTRATOR')")
public class BeanshellController implements InitializingBean {
public static final Logger LOG = LoggerFactory.getLogger(BeanshellController.class);
@Autowired
private GenesysService genesysService;
@Autowired
private GenesysFilterService filterService;
@Autowired
private GenesysApi glisGenesys;
@PersistenceContext
private EntityManager entityManager;
@Autowired
private JdbcTemplate jdbcTemplate;
private ObjectMapper mapper;
private Map<String, Object> exposedBeans;
@Override
public void afterPropertiesSet() throws Exception {
mapper = new ObjectMapper();
mapper.disable(SerializationFeature.EAGER_SERIALIZER_FETCH);
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
this.exposedBeans = new HashMap<>();
this.exposedBeans.put("json", mapper);
this.exposedBeans.put("genesysService", genesysService);
this.exposedBeans.put("filterService", filterService);
this.exposedBeans.put("glisGenesys", glisGenesys);
this.exposedBeans.put("entityManager", entityManager);
this.exposedBeans.put("jdbc", jdbcTemplate);
}
/**
* Render the form.
*/
@RequestMapping(method = RequestMethod.GET)
public ModelAndView defaultView(ModelMap model) {
model.addAttribute("beans", this.exposedBeans);
return new ModelAndView("/admin/beanshell", model);
}
/**
* Execute script in read-only transaction.
*
* @param model
* @param script
* @return
*/
@Transactional(readOnly = true, noRollbackFor = Exception.class)
@RequestMapping(method = RequestMethod.POST, params = { "readonly", "script" })
public String bshExecReadonly(RedirectAttributes redirectAttributes, @RequestParam(name = "script") String script) {
LOG.debug("Executing script in readonly mode\n{}", script);
return executeScript(redirectAttributes, script);
}
private String executeScript(RedirectAttributes redirectAttributes, String script) {
redirectAttributes.addFlashAttribute("script", script);
try {
ConsoleInterface console = new MemoryConsole();
Interpreter i = initializeInterpreter(console);
Object result = i.eval(script);
redirectAttributes.addFlashAttribute("result", result);
try {
redirectAttributes.addFlashAttribute("resultJson", mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result));
} catch (Throwable e) {
redirectAttributes.addFlashAttribute("resultJson", "Could not convert to JSON: " + e.getMessage());
}
redirectAttributes.addFlashAttribute("console", console.toString());
} catch (final EvalError e) {
LOG.error(e.getMessage());
redirectAttributes.addFlashAttribute("error", new BeanshellError(e));
} catch (final Throwable e) {
LOG.error(e.getMessage(), e);
redirectAttributes.addFlashAttribute("exception", new BeanshellError(e));
}
return "redirect:/admin/bsh";
}
protected Interpreter initializeInterpreter(ConsoleInterface console) throws EvalError {
final Interpreter i = new Interpreter(console);
// beans
exposedBeans.entrySet().stream().forEach(entry -> {
try {
i.set(entry.getKey(), entry.getValue());
} catch (EvalError e) {
LOG.error("Trouble initializing bsh interpreter with key={}: {}", entry.getKey(), e.getMessage(), e);
}
});
// imports
i.eval("import org.genesys2.server.model.genesys.*;");
return i;
}
/**
* Byte array backed Beanshell console
*/
public static class MemoryConsole implements ConsoleInterface {
private final ByteArrayOutputStream baos = new ByteArrayOutputStream(1024 * 1024);
private final PrintStream stdout = new PrintStream(baos, true);
private final PrintStream stderr = new PrintStream(baos, true);
@Override
public Reader getIn() {
return null;
}
@Override
public PrintStream getOut() {
return stdout;
}
@Override
public PrintStream getErr() {
return stderr;
}
@Override
public void println(Object o) {
stdout.println(o);
}
@Override
public void print(Object o) {
stdout.print(o);
}
@Override
public void error(Object o) {
stderr.println(o);
}
@Override
public String toString() {
return baos.toString();
}
}
/**
* Serializable exception wrapper for flash attributes
*/
public static class BeanshellError implements Serializable {
private static final long serialVersionUID = 6436834633021529446L;
private String message;
private StackTraceElement[] stackTrace;
private int errorLineNumber;
private String errorText;
private String cause;
private String scriptStackTrace;
private String errorClass;
public BeanshellError(Throwable e) {
this.errorClass = e.getClass().getName();
this.message = e.getMessage();
this.stackTrace = e.getStackTrace();
if (e instanceof EvalError) {
EvalError ee = (EvalError) e;
this.errorLineNumber = ee.getErrorLineNumber();
this.errorText = ee.getErrorText();
this.scriptStackTrace = ee.getScriptStackTrace();
if (ee.getCause() != null)
this.cause = ee.getCause().getMessage();
}
}
public String getMessage() {
return message;
}
public StackTraceElement[] getStackTrace() {
return stackTrace;
}
public String getCause() {
return cause;
}
public int getErrorLineNumber() {
return errorLineNumber;
}
public String getErrorText() {
return errorText;
}
public String getScriptStackTrace() {
return scriptStackTrace;
}
public String getErrorClass() {
return errorClass;
}
}
}
......@@ -749,6 +749,7 @@ menu.admin.repository=Repository
menu.admin.repository.files=Repository file manager
menu.admin.repository.galleries=Image galleries
menu.admin.hazelcast=Hazelcast
menu.admin.beanshell=Beanshell
news.content.page.all.title=News list
news.archive.title=News Archive
......
......@@ -3,171 +3,213 @@
<!DOCTYPE html>
<html lang="${pageContext.response.locale.language}" dir="${pageContext.response.locale.language=='fa' || pageContext.response.locale.language=='ar' ? 'rtl' : 'ltr'}" class="genesys-page">
<head>
<title><sitemesh:write property="title" /></title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="language" content="${pageContext.response.locale.language}" />
<meta name="robots" content="noindex, nofollow" />
<!-- CSRF protection-->
<meta name="_csrf" content="${_csrf.token}" />
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" content="${_csrf.headerName}" />
<!-- Links -->
<link rel="shortcut icon" href="<c:url value="${cdnServers.next}/html/1/images/genesys.png" />" />
<!-- opensearch.org -->
<link rel="search" hreflang="${pageContext.response.locale.language}" type="application/opensearchdescription+xml" href="<c:url value="/acn/opensearch/desc" />"
title="<spring:message code="search.input.placeholder" />" />
<!-- l10n -->
<link rel="alternate" hreflang="en" href="<c:url value="/en${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="ar" href="<c:url value="/ar${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="de" href="<c:url value="/de${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="es" href="<c:url value="/es${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="fa" href="<c:url value="/fa${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="fr" href="<c:url value="/fr${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="pt" href="<c:url value="/pt${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="ru" href="<c:url value="/ru${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="zh" href="<c:url value="/zh${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<!-- Custom styles for this template -->
<%@ include file="css.jsp" %>
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<head>
<title><sitemesh:write property="title"/></title>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="language" content="${pageContext.response.locale.language}"/>
<meta name="robots" content="noindex, nofollow"/>
<!-- CSRF protection-->
<meta name="_csrf" content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" content="${_csrf.headerName}"/>
<!-- Links -->
<link rel="shortcut icon" href="<c:url value='${cdnServers.next}/html/1/images/genesys.png' />" />
<!-- opensearch.org -->
<link rel="search" hreflang="${pageContext.response.locale.language}" type="application/opensearchdescription+xml" href="<c:url value='/acn/opensearch/desc' />" title="<spring:message code='search.input.placeholder' />"/>
<!-- l10n -->
<link rel="alternate" hreflang="en" href="<c:url value='/en${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}' />"/>
<link rel="alternate" hreflang="ar" href="<c:url value='/ar${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}' />"/>
<link rel="alternate" hreflang="de" href="<c:url value='/de${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}' />"/>
<link rel="alternate" hreflang="es" href="<c:url value='/es${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}' />"/>
<link rel="alternate" hreflang="fa" href="<c:url value='/fa${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}' />"/>
<link rel="alternate" hreflang="fr" href="<c:url value='/fr${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}' />"/>
<link rel="alternate" hreflang="pt" href="<c:url value='/pt${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}' />"/>
<link rel="alternate" hreflang="ru" href="<c:url value='/ru${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}' />"/>
<link rel="alternate" hreflang="zh" href="<c:url value='/zh${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}' />"/>
<!-- Custom styles for this template -->
<%@ include file="css.jsp" %>
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
<sitemesh:write property="head" />
<meta name="author" content="Global Crop Diversity Trust" />
</head>
<security:authentication var="user" property="principal" />
<![endif]-->
<sitemesh:write property="head"/>
<meta name="author" content="Global Crop Diversity Trust"/>
</head>
<security:authentication var="user" property="principal"/>
<body>
<%@ include file="header.jsp" %>
<div class="admin-page">
<nav class="navbar navbar-default navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="humburger-btn navbar-toggle collapsed pull-left" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<%--<a class="navbar-brand" href="<c:url value='/admin/' />"> <spring:message code="page.home.title" /> </a>--%>
<a class="navbar-brand nav-logo clearfix" href="<c:url value='/admin/' />"><img src="<c:url value='${cdnServers.next}/html/1/images/GENESYS-ICON.svg' />"/><img src="/html/1/images/GENESYS-LOGO.svg"/></a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<div class="col-md-7 col-lg-10 no-space">
<div class="navbar-header">
<button type="button" class="humburger-btn navbar-toggle collapsed pull-left" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<%--<a class="navbar-brand" href="<c:url value='/admin/' />"> <spring:message code="page.home.title" /> </a>--%>
<a class="navbar-brand nav-logo clearfix" href="<c:url value='/admin/' />"><img src="<c:url value='${cdnServers.next}/html/1/images/GENESYS-ICON.svg' />"/><img src="<c:url value='${cdnServers.next}/html/1/images/GENESYS-LOGO.svg' />"/></a>
</div>
<ul class="nav navbar-nav nav-menu-admin">
<li class="hidden-sm">
<a href="<c:url value='/welcome' />" title="<spring:message code='menu.home' />">
<spring:message code="menu.home"/>
</a>
</li>
<li class="dropdown" id="user-management-menu-item">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
Users
<span class="caret"></span>
</a>
<ul class="dropdown-menu pull-left">
<li>
<a href="<c:url value='/admin/users/' />">
<spring:message code="user.pulldown.users"/></a>
</li>
<li>
<a href="<c:url value='/admin/teams/' />">
<spring:message code="user.pulldown.teams"/></a>
</li>
<li>
<a href="<c:url value='/admin/oauth-clients/' />" class="">OAuth clients</a>
</li>
</ul>
</li>
<li class="dropdown" id="repository-menu-item">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<spring:message code="menu.admin.repository"/>
<span class="caret"></span>
</a>
<ul class="dropdown-menu pull-left">
<li>
<a href="<c:url value='/admin/r/files/' />">
<spring:message code="menu.admin.repository.files"/>
</a>
</li>
<li>
<a href="<c:url value='/admin/r/g' />">
<spring:message code="menu.admin.repository.galleries"/>
</a>
</li>
</ul>
</li>
<li class="dropdown" id="cluster-menu-item">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
Cluster
<span class="caret"></span>
</a>
<ul class="dropdown-menu pull-left">
<li id="hazelcast-menu-item">
<a class="" href="<c:url value='/admin/hazelcast/' />">
<spring:message code="menu.admin.hazelcast"/></a>
</li>
<li id="cache-menu-item">
<a class="" href="<c:url value='/admin/cache/' />">
<spring:message code="menu.admin.caches"/>
</a>
</li>
<li>
<a href="<c:url value='/admin/elastic/' />" class="">Elasticsearch</a>
</li>
</ul>
</li>
<li class="dropdown" id="cluster-menu-item">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
Data
<span class="caret"></span>
</a>
<ul class="dropdown-menu pull-left">
<li id="ds2-menu-item">
<a class="" href="<c:url value='/admin/ds2/' />">
<spring:message code="menu.admin.ds2"/>
</a>
</li>
<li id="kpi-menu-item">
<a class="" href="<c:url value='/admin/kpi/' />">
<spring:message code="menu.admin.kpi"/>
</a>
</li>
</ul>
</li>
<li id="kpi-menu-item">
<a class="" href="<c:url value='/admin/bsh' />">
<spring:message code="menu.admin.beanshell"/>
</a>
</li>
<li id="logger-menu-item">
<a class="" href="<c:url value='/admin/logger/' />">
<spring:message code="menu.admin.loggers"/>
</a>
</li>
</ul>
</div>
<body>
<%@ include file="header.jsp" %>
<div class="col-md-5 col-lg-2 no-space">
<ul class="nav navbar-nav navbar-right">
<%@ include file="languages.jspf" %>
</ul>
</div>
</div>
</div>
</nav>
</div>
<div class="admin-page">
<nav class="navbar navbar-default navbar-inverse navbar-fixed-top">
<!-- Begin page content -->
<div id="content" class="<sitemesh:write property='body.class' />">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="humburger-btn navbar-toggle collapsed pull-left" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<%--<a class="navbar-brand" href="<c:url value="/admin/" />"> <spring:message code="page.home.title" /> </a>--%>
<a class="navbar-brand nav-logo clearfix" href="<c:url value="/admin/" />"><img src="<c:url value="${cdnServers.next}/html/1/images/GENESYS-ICON.svg" />" /><img src="/html/1/images/GENESYS-LOGO.svg" /></a>
<div id="content-header" class="row">
<sitemesh:write property="page.header"/>
</div>
<div id="navbar" class="collapse navbar-collapse">
<div class="col-md-7 col-lg-10 no-space">
<div class="navbar-header">
<button type="button" class="humburger-btn navbar-toggle collapsed pull-left" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<%--<a class="navbar-brand" href="<c:url value="/admin/" />"> <spring:message code="page.home.title" /> </a>--%>
<a class="navbar-brand nav-logo clearfix" href="<c:url value="/admin/" />"><img src="<c:url value="${cdnServers.next}/html/1/images/GENESYS-ICON.svg" />" /><img src="<c:url value="${cdnServers.next}/html/1/images/GENESYS-LOGO.svg" />" /></a>
</div>
<ul class="nav navbar-nav nav-menu-admin">
<li class="hidden-sm">
<a href="<c:url value="/welcome" />" title="<spring:message code="menu.home" />"> <spring:message code="menu.home" />
</a>
</li>
<li id="cache-menu-item">
<a class="" href="<c:url value="/admin/cache/" />"> <spring:message code="menu.admin.caches" />
</a>
</li>
<li>
<a href="<c:url value="/admin/elastic/" />" class="">Elasticsearch</a>
</li>
<li class="dropdown" id="user-management-menu-item" >
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> <spring:message code="menu.admin.usermanagement" /> <span class="caret"></span></a>
<ul class="dropdown-menu pull-left">
<li>
<a href="<c:url value="/admin/users/" />"> <spring:message code="user.pulldown.users" /></a>
</li>
<li>
<a href="<c:url value="/admin/teams/" />"> <spring:message code="user.pulldown.teams" /></a>
</li>
<li>
<a href="<c:url value="/admin/oauth-clients/" />" class="">OAuth clients</a>
</li>
</ul>
</li>
<li class="dropdown" id="repository-menu-item">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> <spring:message code="menu.admin.repository" /> <span class="caret"></span>
</a>
<ul class="dropdown-menu pull-left">
<li>
<a href="<c:url value="/admin/r/files/" />"> <spring:message code="menu.admin.repository.files" />
</a>
</li>
<li>
<a href="<c:url value="/admin/r/g" />"> <spring:message code="menu.admin.repository.galleries" />
</a>
</li>
</ul>
</li>
<li id="logger-menu-item">
<a class="" href="<c:url value="/admin/logger/" />"> <spring:message code="menu.admin.loggers" />
</a>
</li>
<li id="hazelcast-menu-item">
<a class="" href="<c:url value="/admin/hazelcast/" />"> <spring:message code="menu.admin.hazelcast" /></a>
</li>
<li id="ds2-menu-item">
<a class="" href="<c:url value="/admin/ds2/" />"> <spring:message code="menu.admin.ds2" />
</a>
</li>
<li id="kpi-menu-item">
<a class="" href="<c:url value="/admin/kpi/" />"> <spring:message code="menu.admin.kpi" />
</a>
</li>
</ul>
<div id="content-body" class="">
<div class