/** * Licensed to JumpMind Inc under one or more contributor * license agreements. See the NOTICE file distributed * with this work for additional information regarding * copyright ownership. JumpMind Inc licenses this file * to you under the GNU General Public License, version 3.0 (GPLv3) * (the "License"); you may not use this file except in compliance * with the License. * * You should have received a copy of the GNU General Public License, * version 3.0 (GPLv3) along with this library; if not, see * <http://www.gnu.org/licenses/>. * * 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.jumpmind.symmetric.load; import java.util.List; import java.util.Map; import java.util.Set; import org.jumpmind.db.model.Table; import org.jumpmind.symmetric.ISymmetricEngine; import org.jumpmind.symmetric.SymmetricException; import org.jumpmind.symmetric.io.data.CsvData; import org.jumpmind.symmetric.io.data.DataContext; import org.jumpmind.symmetric.io.data.DataEventType; import org.jumpmind.symmetric.model.LoadFilter; import org.jumpmind.util.Context; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import bsh.EvalError; import bsh.Interpreter; import bsh.ParseException; import bsh.TargetError; public class BshDatabaseWriterFilter extends DynamicDatabaseWriterFilter { private static final String OLD_ = "OLD_"; private static final String CONTEXT = "context"; private static final String TABLE = "table"; private static final String DATA = "data"; private static final String ERROR = "error"; private static final String ENGINE = "engine"; private static final String LOG = "log"; private final String INTERPRETER_KEY = String.format("%d.BshInterpreter", hashCode()); protected final Logger log = LoggerFactory.getLogger(getClass()); public BshDatabaseWriterFilter(ISymmetricEngine engine, Map<String, List<LoadFilter>> loadFilters) { super(engine, loadFilters); } @Override protected boolean processLoadFilters(DataContext context, Table table, CsvData data, Exception error, WriteMethod writeMethod, List<LoadFilter> loadFiltersForTable) { boolean writeRow = true; LoadFilter currentFilter = null; try { Interpreter interpreter = getInterpreter(context); bind(interpreter, context, table, data, error); for (LoadFilter filter : loadFiltersForTable) { currentFilter = filter; if (filter.isFilterOnDelete() && data.getDataEventType().equals(DataEventType.DELETE) || filter.isFilterOnInsert() && data.getDataEventType().equals(DataEventType.INSERT) || filter.isFilterOnUpdate() && data.getDataEventType().equals(DataEventType.UPDATE)) { Object result = null; if (writeMethod.equals(WriteMethod.BEFORE_WRITE) && filter.getBeforeWriteScript() != null) { result = interpreter.eval(filter.getBeforeWriteScript()); } else if (writeMethod.equals(WriteMethod.AFTER_WRITE) && filter.getAfterWriteScript() != null) { result = interpreter.eval(filter.getAfterWriteScript()); } else if (writeMethod.equals(WriteMethod.HANDLE_ERROR) && filter.getHandleErrorScript() != null) { result = interpreter.eval(filter.getHandleErrorScript()); } if (result != null && result.equals(Boolean.FALSE)) { writeRow = false; } } } } catch (EvalError ex) { processError(currentFilter, table, ex); } return writeRow; } @Override protected void executeScripts(DataContext context, String key, Set<String> scripts, boolean isFailOnError) { Interpreter interpreter = getInterpreter(context); String currentScript = null; try { bind(interpreter, context, null, null, null); if (scripts != null) { for (String script : scripts) { currentScript = script; interpreter.eval(script); } } } catch (EvalError e) { if (e instanceof ParseException) { String errorMsg = String .format("Evaluation error while parsing the following beanshell script:\n\n%s\n\nThe error was on line %d and the error message was: %s", currentScript, e.getErrorLineNumber(), e.getMessage()); log.error(errorMsg, e); if (isFailOnError) { throw new SymmetricException(errorMsg); } } else if (e instanceof TargetError) { Throwable target = ((TargetError) e).getTarget(); String errorMsg = String .format("Evaluation error occured in the following beanshell script:\n\n%s\n\nThe error was on line %d", currentScript, e.getErrorLineNumber()); log.error(errorMsg, target); if (isFailOnError) { if (target instanceof RuntimeException) { throw (RuntimeException) target; } else { throw new SymmetricException(target); } } else { log.error("Failed while evaluating script", target); } } } } protected Interpreter getInterpreter(Context context) { Interpreter interpreter = (Interpreter) context.get(INTERPRETER_KEY); if (interpreter == null) { interpreter = new Interpreter(); context.put(INTERPRETER_KEY, interpreter); } return interpreter; } protected void bind(Interpreter interpreter, DataContext context, Table table, CsvData data, Exception error) throws EvalError { interpreter.set(LOG, log); interpreter.set(ENGINE, this.engine); interpreter.set(CONTEXT, context); interpreter.set(TABLE, table); interpreter.set(DATA, data); interpreter.set(ERROR, error); if (data != null) { Map<String, String> sourceValues = data.toColumnNameValuePairs(table.getColumnNames(), CsvData.ROW_DATA); if (sourceValues.size() > 0) { for (String columnName : sourceValues.keySet()) { interpreter.set(columnName, sourceValues.get(columnName)); interpreter.set(columnName.toUpperCase(), sourceValues.get(columnName)); } } else { Map<String, String> pkValues = data.toColumnNameValuePairs( table.getPrimaryKeyColumnNames(), CsvData.PK_DATA); for (String columnName : pkValues.keySet()) { interpreter.set(columnName, pkValues.get(columnName)); interpreter.set(columnName.toUpperCase(), pkValues.get(columnName)); } } Map<String, String> oldValues = data.toColumnNameValuePairs(table.getColumnNames(), CsvData.OLD_DATA); for (String columnName : oldValues.keySet()) { interpreter.set(OLD_ + columnName, sourceValues.get(columnName)); interpreter.set(OLD_ + columnName.toUpperCase(), sourceValues.get(columnName)); } } } protected void processError(LoadFilter currentFilter, Table table, Throwable ex) { if (ex instanceof TargetError) { ex = ((TargetError) ex).getTarget(); } String formattedMessage = String.format( "Error executing beanshell script for load filter %s on table %s. The error was: %s", new Object[] { currentFilter != null ? currentFilter.getLoadFilterId() : "N/A", table.getName(), ex.getMessage() }); log.error(formattedMessage); if (currentFilter.isFailOnError()) { throw new SymmetricException(formattedMessage, ex); } } }