001package com.randomnoun.common.db.history; 002 003/* (c) 2013 randomnoun. All Rights Reserved. This work is licensed under a 004 * BSD Simplified License. (http://www.randomnoun.com/bsd-simplified.html) 005 */ 006 007import java.io.ByteArrayOutputStream; 008import java.io.PrintWriter; 009import java.util.HashMap; 010import java.util.Map; 011 012import javax.script.ScriptContext; 013import javax.script.ScriptEngine; 014import javax.script.ScriptEngineManager; 015import javax.script.ScriptException; 016import javax.script.SimpleScriptContext; 017import javax.sql.DataSource; 018 019import org.apache.log4j.Logger; 020 021import com.randomnoun.common.Text; 022import com.randomnoun.common.db.DatabaseReader; 023import com.randomnoun.common.db.dao.MysqlDatabaseReader; 024import com.randomnoun.common.db.to.SchemaTO; 025 026/** This class will generate history table and triggers. 027 * 028 * If you get these error messages: 029 * <pre> 030 * Access denied; you need the SUPER privilege for this operation 031 * </pre> 032 * 033 * then try this: 034 * <pre> 035 * GRANT ALL PRIVILEGES ON ON schema_name.* TO 'ON schema_name'@'%' WITH GRANT OPTION; 036 * </pre> 037 * 038 * I've got a sqlserver version of this somewhere. Good luck finding that again. 039 * 040 * <p>This class is similar to the old HistoryTableGenerator, but uses templates instead. 041 * 042 * <p><b>TODO</b> add other db types 043 * <p><b>TODO</b> add stored procs to roll back table(s) to a given point in time 044 * <p><b>TODO</b> add history partitions, if mysql supports it 045 **/ 046public class HistoryTableGenerator { 047 048 Logger logger = Logger.getLogger(HistoryTableGenerator.class); 049 050 Logger scriptLogger = Logger.getLogger("com.randomnoun.common.db.HistoryTableGenerator2.script"); 051 052 private DataSource ds; 053 054 private String jessopScript; 055 private String jessopScriptFilename; 056 private String schemaName; 057 private Map<String, Object> options = new HashMap<String, Object>(); 058 059 public HistoryTableGenerator(DataSource ds) { 060 this.ds = ds; 061 } 062 063 /** Set the options for the history generator. 064 * 065 * <p>History options are specific to the generator being used, but typical options are: 066 * 067 * <ul> 068 * <li>undoEnabledTableNames - List<String> 069 * <li>dropTables - Boolean 070 * <li>existingDataUserActionId - Boolean 071 * <li>alwaysRecreateTriggers - Boolean 072 * <li>alwaysRecreateStoredProcedures - Boolean 073 * <li>includeUpdateBitFields - Boolean 074 * <li>includeCurrentUser - Boolean 075 * </ul> 076 * 077 * <p>Refer to the source code of the generator as to which options are supported 078 * ( e.g. src/main/resources/common/db/mysql/mysql-historyTable-2.sql.jessop ) 079 * 080 * @param options 081 */ 082 public void setOptions(Map<String, Object> options) { 083 this.options = options; 084 } 085 086 087 /* * When the logger in this class is set to log at DEBUG level, then this method returns the 088 * jessop script transpiled to whichever language it's supposed to be in. 089 * 090 * @param engine 091 * @param jessopSource 092 * @return 093 * @throws ScriptException 094 * 095 private String getSource(ScriptEngine engine, String jessopSource) throws ScriptException { 096 Compilable compilable = (Compilable) engine; 097 JessopCompiledScript compiledScript = (JessopCompiledScript) compilable.compile(jessopSource); 098 return compiledScript.getSource(); 099 } 100 */ 101 102 /** Returns the SQL that will create history tables, triggers and stored procedures. 103 * 104 * <p>To break this String back into individual SQL statements, use {@link com.randomnoun.common.db.SqlParser} 105 * 106 * @return SQL that will create history tables, triggers and stored procedures 107 * 108 * @throws ScriptException 109 */ 110 public String generateHistoryTableSql() throws ScriptException { 111 112 DatabaseReader dr = new MysqlDatabaseReader(ds); 113 SchemaTO schema = dr.getSchema(schemaName); 114 115 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 116 PrintWriter pw = new PrintWriter(baos); 117 118 ScriptEngine engine = new ScriptEngineManager().getEngineByName("jessop"); 119 if (engine==null) { throw new IllegalStateException("Missing engine 'jessop'"); } 120 engine.put(ScriptEngine.FILENAME, jessopScriptFilename); 121 122 ScriptContext sc = new SimpleScriptContext(); 123 sc.setWriter(pw); 124 sc.setAttribute("schema", schema, ScriptContext.ENGINE_SCOPE); 125 126 sc.setAttribute("logger", scriptLogger, ScriptContext.ENGINE_SCOPE); 127 sc.setAttribute("options", options, ScriptContext.ENGINE_SCOPE); 128 129 logger.info("Start eval"); 130 jessopScript = Text.replaceString(jessopScript, "\r", ""); // jessop has issues with \r\n EOLs 131 // if (logger.isDebugEnabled()) { logger.debug(getSource(engine, jessopScript)); } 132 133 // table loop is within the generator now 134 engine.eval(jessopScript, sc); 135 136 String sql = baos.toString(); 137 return sql; 138 } 139 140 public String getJessopScript() { 141 return jessopScript; 142 } 143 144 public void setJessopScript(String jessopScript) { 145 this.jessopScript = jessopScript; 146 } 147 148 public String getJessopScriptFilename() { 149 return jessopScriptFilename; 150 } 151 152 public void setJessopScriptFilename(String jessopScriptFilename) { 153 this.jessopScriptFilename = jessopScriptFilename; 154 } 155 156 public String getSchemaName() { 157 return schemaName; 158 } 159 160 public void setSchemaName(String schemaName) { 161 this.schemaName = schemaName; 162 } 163 164 public Map<String, Object> getOptions() { 165 return options; 166 } 167 168 169 170 171}