/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 de.unioninvestment.eai.portal.support.scripting;
import com.google.common.base.Strings;
import de.unioninvestment.eai.portal.portlet.crud.config.*;
import de.unioninvestment.eai.portal.portlet.crud.config.SelectConfig.Dynamic;
import de.unioninvestment.eai.portal.portlet.crud.domain.exception.BusinessException;
import de.unioninvestment.eai.portal.portlet.crud.domain.util.Util;
import groovy.lang.Script;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.List;
/**
* Diese Klasse dient der Kompilierung aller Groovy-Scripte, die in der
* XML-Konfiguration hinterlegt sind. Entsprechende Stellen im JAXB-Modell der
* Konfiguration verwenden einen Konverter in die Klasse {@link GroovyScript},
* so dass die Kompilate im Modell gespeichert und gecached werden können.
*
* @author carsten.mjartan
*/
@Component
public class ConfigurationScriptsCompiler {
private static final Logger LOG = LoggerFactory
.getLogger(ConfigurationScriptsCompiler.class);
private final ScriptCompiler compiler;
/**
* @param compiler
* an den für die eigentliche Kompilierung delegiert wird.
*/
@Autowired
public ConfigurationScriptsCompiler(ScriptCompiler compiler) {
this.compiler = compiler;
}
/**
* Traversiert den JAXB-Objektbaum und kompiliert alle Scripte.
*
* @param config
* das Haupt-Objekt des JAXB-Konfigurationsmodells
*/
public void compileAllScripts(PortletConfig config) {
if (!compileScripts(config.getScript(), "/portlet/script")) {
compileNoopScriptClass(config);
}
compileAllClosureScripts(config);
}
private void compileAllClosureScripts(PortletConfig config) {
compileClosure(config.getOnLoad(), "/onLoad");
compileClosure(config.getOnReload(), "/onReload");
compileClosure(config.getOnRefresh(), "/onRefresh");
if (config.getTabs() != null) {
compileAllClosureScripts(config.getTabs(), "");
} else {
compileAllClosureScripts(config.getPage(), "");
}
List<DialogConfig> dialogConfigList = config.getDialog();
if (dialogConfigList != null) {
for (DialogConfig dialogConfig : dialogConfigList) {
compileAllClosureScripts(dialogConfig, "");
}
}
}
private void compileAllClosureScripts(TabsConfig tabs, String location) {
compileClosure(tabs.getOnChange(), location + "/onChange");
int i = 0;
for (PanelConfig panel : tabs.getTab()) {
compileAllClosureScripts(panel, location + "/tab[" + i++ + "]");
}
}
private void compileAllClosureScripts(PanelConfig panel, String location) {
if (panel instanceof TabConfig) {
TabConfig tab = (TabConfig) panel;
compileClosure(tab.getOnShow(), location + "/onShow");
compileClosure(tab.getOnHide(), location + "/onHide");
} else if (panel instanceof RegionConfig) {
RegionConfig region = (RegionConfig) panel;
compileClosure(region.getOnExpand(), location + "/onExpand");
compileClosure(region.getOnCollapse(), location + "/onCollapse");
}
int i = 0;
for (ComponentConfig component : panel.getElements()) {
if (component instanceof CompoundSearchConfig) {
CompoundSearchDetailsConfig details = ((CompoundSearchConfig) component).getDetails();
if (details != null) {
compileAllClosureScripts(
details,
location + "/elements[" + i++ + "]/details");
}
} else if (component instanceof TableConfig) {
compileAllClosureScripts((TableConfig) component, location
+ "/elements[" + i++ + "]");
} else if (component instanceof FormConfig) {
compileAllClosureScripts((FormConfig) component, location
+ "/elements[" + i++ + "]");
} else if (component instanceof TabsConfig) {
compileAllClosureScripts((TabsConfig) component, location
+ "/elements[" + i++ + "]");
} else if (component instanceof ScriptComponentConfig) {
compileComponentGeneratorScript(
(ScriptComponentConfig) component, location
+ "/elements[" + i++ + "]");
} else if (component instanceof RegionConfig) {
compileAllClosureScripts((RegionConfig) component, location
+ "/elements[" + i++ + "]");
}
}
}
private void compileComponentGeneratorScript(
ScriptComponentConfig component, String location) {
compileClosure(component.getGenerator(), "builder", location
+ "/generator");
}
private void compileAllClosureScripts(TableConfig table, String location) {
compileTableOnModeChangeScript(table, location);
compileTableOnSelectionChangeScript(table, location);
compileTableOnDoubleClickScript(table, location);
compileTableOnInitializeScript(table, location);
compileTableActionScripts(table, location);
compileContainerScript(table, location);
compileRowStyleScript(table, location);
compileTableOnRowChangeScript(table, location);
compileClosure(table.getRowValidator(), "it,row", location
+ "/row-validator");
compileColumnScripts(table, location);
compileRowEditableClosure(table, location);
}
private void compileColumnScripts(TableConfig table, String location) {
if (table.getColumns() != null) {
for (ColumnConfig column : table.getColumns().getColumn()) {
compileClosure(column.getStyle(), "row, columnName", location
+ "/columns/column/style");
if (column.getSelect() != null
&& column.getSelect().getDynamic() != null) {
compileAllClosureScripts(column.getSelect().getDynamic(),
location + "/columns/" + column.getName()
+ "/select/dynamic");
}
compileGString(column.getDefault(), "now", location
+ "/columns/column/default");
GroovyScript editableClosure = column.getEditable();
if (editableClosure != null
&& !Util.isPlainBoolean(editableClosure)) {
compileClosure(editableClosure, "table, columnName, row",
location + "/columns/column/editable");
}
compileClosure(column.getValidator(), "it,value", location
+ "/columns/column/validator");
compileClosure(column.getGenerator(), "row,builder", location
+ "/columns/column/generator");
compileClosure(column.getGeneratedValue(), "row", location
+ "/columns/column/generated-value");
}
}
}
private void compileRowStyleScript(TableConfig table, String location) {
if (table.getRowStyle() != null) {
compileClosure(table.getRowStyle(), "row", location + "/rowStyle");
}
}
private void compileContainerScript(TableConfig table, String location) {
if (table.getDatabaseQuery() != null) {
compileAllClosureScripts(table.getDatabaseQuery(), location
+ "/databaseQuery");
} else if (table.getDatabaseTable() != null) {
compileAllClosureScripts(table.getDatabaseTable(), location
+ "/databaseTable");
} else if (table.getScriptContainer() != null) {
compileAllClosureScripts(table.getScriptContainer(), location
+ "/scriptContainer");
} else if (table.getRestContainer() != null) {
compileAllClosureScripts(table.getRestContainer(), location
+ "/scriptContainer");
}
}
private void compileTableActionScripts(TableConfig table, String location) {
int i = 0;
for (TableActionConfig action : table.getAction()) {
compileClosure(action.getOnExecution(), location + "/action[" + i++
+ "]/onExecution");
if (action.getExport() != null) {
compileClosure(action.getExport().getFilename(), "it", location
+ "/action[" + i + "]/export/filename");
}
if (action.getDownload() != null) {
// "export" for backwards compatibility (deprecated)
compileClosure(action.getDownload().getGenerator(),
"it,factory,export", location + "/action[" + i
+ "]/download/generator");
}
}
}
private void compileTableOnSelectionChangeScript(TableConfig table,
String location) {
compileClosure(table.getOnSelectionChange(), "it,selection", location
+ "/onSelectionChange");
}
private void compileTableOnInitializeScript(TableConfig table,
String location) {
compileClosure(table.getOnInitialize(), "it", location
+ "/onInitialize");
}
private void compileTableOnDoubleClickScript(TableConfig table,
String location) {
compileClosure(table.getOnDoubleClick(), "it,row", location
+ "/onDoubleClick");
}
private void compileTableOnModeChangeScript(TableConfig table,
String location) {
compileClosure(table.getOnModeChange(), "it,mode", location
+ "/onModeChange");
}
private void compileTableOnRowChangeScript(TableConfig table,
String location) {
compileClosure(table.getOnRowChange(), "it,row,changedValues", location
+ "/onRowChange");
}
private void compileRowEditableClosure(TableConfig table, String location) {
compileClosure(table.getRowEditable(), "it,row", location
+ "/row-editable");
compileClosure(table.getRowDeletable(), "it,row", location
+ "/row-deletable");
}
private void compileAllClosureScripts(Dynamic dynamicSelect, String location) {
compileClosure(dynamicSelect.getOptions(), "row,columnName", location
+ "/options");
}
private void compileAllClosureScripts(ContainerConfig container,
String location) {
compileClosure(container.getOnCommit(), location + "/onCommit");
compileClosure(container.getOnCreate(), "it,row", location
+ "/onCreate");
compileClosure(container.getOnInsert(), "it,row", location
+ "/onInsert");
compileClosure(container.getOnDelete(), "it,row", location
+ "/onDelete");
compileClosure(container.getOnUpdate(), "it,row", location
+ "/onUpdate");
if (container instanceof DatabaseQueryConfig) {
DatabaseQueryConfig config = (DatabaseQueryConfig) container;
compileDatabaseQueryScripts(config, location);
} else if (container instanceof ScriptContainerConfig) {
ScriptContainerConfig config = (ScriptContainerConfig) container;
compileScriptContainerScripts(config, location);
} else if (container instanceof ReSTContainerConfig) {
ReSTContainerConfig config = (ReSTContainerConfig) container;
compileReSTContainerScripts(config, location);
}
}
private void compileReSTContainerScripts(ReSTContainerConfig config,
String location) {
compileClosure(config.getQuery().getCollection(), location
+ "/query/collection");
compileReSTAttributeScripts(config.getQuery().getAttribute(), location
+ "/query/attributes");
compileGString(config.getBaseUrl(), "", location + "/baseUrl");
compileGString(config.getQuery().getUrl(), "", location + "/query/url");
if (config.getInsert() != null) {
compileGString(config.getInsert().getUrl(), "row", location
+ "/insert/url");
compileClosure(config.getInsert().getValue(), "row", location
+ "/insert/value");
}
if (config.getUpdate() != null) {
compileGString(config.getUpdate().getUrl(), "row", location
+ "/update/url");
compileClosure(config.getUpdate().getValue(), "row", location
+ "/update/value");
}
if (config.getDelete() != null) {
compileGString(config.getDelete().getUrl(), "row", location
+ "/delete/url");
}
}
private void compileReSTAttributeScripts(
List<ReSTAttributeConfig> attributes, String location) {
for (ReSTAttributeConfig attribute : attributes) {
if (attribute.getPath() == null
|| attribute.getPath().getSource() == null) {
attribute.setPath(new GroovyScript(attribute.getName()));
}
compileClosure(attribute.getPath(),
location + "[" + attribute.getName() + "]");
}
}
private void compileScriptContainerScripts(
ScriptContainerConfig scriptContainerConfig, String location) {
compileClosure(scriptContainerConfig.getDelegate(), "", location
+ "/delegate");
}
private void compileDatabaseQueryScripts(DatabaseQueryConfig container,
String location) {
if (container.getInsert() != null) {
// compileStatementScripts(container.getInsert(), location);
compileStatementScriptsWithParameter(container.getInsert(),
location, "container,row,connection",
"container.generateInsertStatement(row)");
}
if (container.getUpdate() != null) {
// compileStatementScripts(container.getUpdate(), location);
compileStatementScriptsWithParameter(container.getUpdate(),
location, "container,row,connection",
"container.generateUpdateStatement(row)");
}
if (container.getDelete() != null) {
compileStatementScriptsWithParameter(container.getDelete(),
location, "container,row,connection",
"container.generateDeleteStatement(row)");
}
}
private void compileStatementScriptsWithParameter(
StatementConfig statement, String location, String parameters, String generatorScript) {
switch (statement.getType()) {
case SQL:
if (Strings.isNullOrEmpty(statement.getStatement().getSource())) {
compileClosure(statement.getStatement(), parameters, location, generatorScript, false);
} else {
compileGString(statement.getStatement(), parameters, location);
}
break;
case SCRIPT:
compileClosure(statement.getStatement(), parameters, location);
break;
default:
throw new UnsupportedOperationException(
"Unbekannter Statement-Typ: " + statement.getType());
}
}
private void compileAllClosureScripts(FormConfig form, String location) {
int i = 0;
for (FormActionConfig action : form.getAction()) {
String actionLocation = location + "/action[" + (i++) + "]";
compileClosure(action.getOnExecution(), actionLocation
+ "/onExecution");
if (action.getSearch() != null
&& action.getSearch().getApplyFilters() != null) {
compileAllFilterClosureScripts(action.getSearch()
.getApplyFilters().getFilters(), actionLocation
+ "/search/apply-filters");
}
}
i = 0;
for (FormFieldConfig formFieldConfig : form.getField()) {
String fieldLocation = location + "/field[" + i++ + "]";
compileClosure(formFieldConfig.getOnValueChange(), fieldLocation
+ "/onValueChange");
if (formFieldConfig.getSelect() != null
&& formFieldConfig.getSelect().getDynamic() != null) {
compileClosure(formFieldConfig.getSelect().getDynamic()
.getOptions(), "it", fieldLocation + "/select/dynamic");
}
}
}
private void compileAllFilterClosureScripts(List<FilterConfig> filters,
String actionLocation) {
int j = 0;
for (FilterConfig filter : filters) {
String filterLocation = actionLocation + "[" + (j++) + "]";
compileAllClosureScripts(filter, filterLocation);
}
}
private void compileAllClosureScripts(FilterConfig filter, String location) {
if (filter instanceof CustomFilterConfig) {
compileClosure(((CustomFilterConfig) filter).getFilter(), "row",
location + "/filter");
} else if (filter instanceof FilterListConfig) {
FilterListConfig filterListConfig = (FilterListConfig) filter;
compileAllFilterClosureScripts(filterListConfig.getFilters(),
"/filters");
}
}
private void compileNoopScriptClass(PortletConfig config) {
ScriptConfig noopScript = new ScriptConfig();
config.getScript().add(noopScript);
noopScript.setValue(new GroovyScript(""));
try {
Class<Script> script = compiler.compileScript("");
noopScript.getValue().setClazz(script);
} catch (ScriptingException e) {
LOG.error("Error compiling main script", e);
throw new BusinessException("portlet.crud.error.compilingScript",
"/portlet/script");
}
}
private boolean compileScripts(List<ScriptConfig> scripts, String location) {
boolean hasMainScript = false;
for (ScriptConfig config : scripts) {
if (config.getProperty() == null) {
hasMainScript = true;
}
String scriptName;
if (config.getSrc() != null) {
scriptName = new File(config.getSrc()).getName();
} else {
String name = config.getProperty();
if (name == null) {
name = "main";
}
scriptName = StringUtils.capitalize(name)
+ "PortletScript.groovy";
}
compileScript(scriptName, config.getValue(), location);
}
return hasMainScript;
}
private boolean compileClosure(GroovyScript groovyScript, String location) {
return compileClosure(groovyScript, "it", location, null, false);
}
private boolean compileClosure(GroovyScript groovyScript,
String parameterNames, String location) {
return compileClosure(groovyScript, parameterNames, location, null, false);
}
private boolean compileGString(GroovyScript groovyScript,
String parameterNames, String location) {
return compileClosure(groovyScript, parameterNames, location, null, true);
}
private boolean compileClosure(GroovyScript groovyScript,
String parameterNames, String location, String defaultSource, boolean wrapSourceAsGString) {
if (groovyScript != null && (!Strings.isNullOrEmpty(groovyScript.getSource()) || defaultSource != null)) {
String source = groovyScript.getSource();
if (Strings.isNullOrEmpty(source)) {
source = defaultSource;
}
String code;
if (!wrapSourceAsGString) {
// { row -> code }
code = "{ " + parameterNames + " -> "
+ source + " }";
} else {
// { row -> """code""" }
code = "{ " + parameterNames + " -> "
+ multilineGStringExpression(source)
+ " }";
}
try {
Class<Script> script = compiler.compileScript(code);
groovyScript.setClazz(script);
return true;
} catch (ScriptingException e) {
LOG.error("Error compiling script at '" + location + "'", e);
throw new BusinessException(
"portlet.crud.error.compilingScript", location);
}
}
return false;
}
private static String multilineGStringExpression(String sqlString) {
return sqlString == null ? null : "\"\"\""
+ sqlString.replace("\"\"\"", "\\\"\\\"\\\"") + "\"\"\"";
}
private boolean compileScript(String name, GroovyScript groovyScript,
String location) {
if (groovyScript != null && groovyScript.getSource() != null) {
String source = groovyScript.getSource();
try {
String sourceWithLogAtEnd = source + //
"\nlog.debug '" + name + " execution finished...'";
Class<Script> script = compiler.compileScript(
sourceWithLogAtEnd, name);
groovyScript.setClazz(script);
return true;
} catch (ScriptingException e) {
LOG.error("Error compiling script at '" + location + "'", e);
throw new BusinessException(
"portlet.crud.error.compilingScript", location);
}
}
return false;
}
}