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}