/* * Copyright 2012 LinkedIn Corp. * * 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 azkaban.trigger; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apache.commons.dbutils.DbUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.ResultSetHandler; import org.apache.log4j.Logger; import org.joda.time.DateTime; import azkaban.database.AbstractJdbcLoader; import azkaban.utils.GZIPUtils; import azkaban.utils.JSONUtils; import azkaban.utils.Props; public class JdbcTriggerLoader extends AbstractJdbcLoader implements TriggerLoader { private static Logger logger = Logger.getLogger(JdbcTriggerLoader.class); private EncodingType defaultEncodingType = EncodingType.GZIP; private static final String triggerTblName = "triggers"; private static final String GET_UPDATED_TRIGGERS = "SELECT trigger_id, trigger_source, modify_time, enc_type, data FROM " + triggerTblName + " WHERE modify_time>=?"; private static String GET_ALL_TRIGGERS = "SELECT trigger_id, trigger_source, modify_time, enc_type, data FROM " + triggerTblName; private static String GET_TRIGGER = "SELECT trigger_id, trigger_source, modify_time, enc_type, data FROM " + triggerTblName + " WHERE trigger_id=?"; private static String ADD_TRIGGER = "INSERT INTO " + triggerTblName + " ( modify_time) values (?)"; private static String REMOVE_TRIGGER = "DELETE FROM " + triggerTblName + " WHERE trigger_id=?"; private static String UPDATE_TRIGGER = "UPDATE " + triggerTblName + " SET trigger_source=?, modify_time=?, enc_type=?, data=? WHERE trigger_id=?"; public EncodingType getDefaultEncodingType() { return defaultEncodingType; } public void setDefaultEncodingType(EncodingType defaultEncodingType) { this.defaultEncodingType = defaultEncodingType; } public JdbcTriggerLoader(Props props) { super(props); } @Override public List<Trigger> getUpdatedTriggers(long lastUpdateTime) throws TriggerLoaderException { logger.info("Loading triggers changed since " + new DateTime(lastUpdateTime).toString()); Connection connection = getConnection(); QueryRunner runner = new QueryRunner(); ResultSetHandler<List<Trigger>> handler = new TriggerResultHandler(); List<Trigger> triggers; try { triggers = runner.query(connection, GET_UPDATED_TRIGGERS, handler, lastUpdateTime); } catch (SQLException e) { logger.error(GET_ALL_TRIGGERS + " failed."); throw new TriggerLoaderException("Loading triggers from db failed. ", e); } finally { DbUtils.closeQuietly(connection); } logger.info("Loaded " + triggers.size() + " triggers."); return triggers; } @Override public List<Trigger> loadTriggers() throws TriggerLoaderException { logger.info("Loading all triggers from db."); Connection connection = getConnection(); QueryRunner runner = new QueryRunner(); ResultSetHandler<List<Trigger>> handler = new TriggerResultHandler(); List<Trigger> triggers; try { triggers = runner.query(connection, GET_ALL_TRIGGERS, handler); } catch (SQLException e) { logger.error(GET_ALL_TRIGGERS + " failed."); throw new TriggerLoaderException("Loading triggers from db failed. ", e); } finally { DbUtils.closeQuietly(connection); } logger.info("Loaded " + triggers.size() + " triggers."); return triggers; } @Override public void removeTrigger(Trigger t) throws TriggerLoaderException { logger.info("Removing trigger " + t.toString() + " from db."); QueryRunner runner = createQueryRunner(); try { int removes = runner.update(REMOVE_TRIGGER, t.getTriggerId()); if (removes == 0) { throw new TriggerLoaderException("No trigger has been removed."); } } catch (SQLException e) { logger.error(REMOVE_TRIGGER + " failed."); throw new TriggerLoaderException("Remove trigger " + t.toString() + " from db failed. ", e); } } @Override public void addTrigger(Trigger t) throws TriggerLoaderException { logger.info("Inserting trigger " + t.toString() + " into db."); t.setLastModifyTime(System.currentTimeMillis()); Connection connection = getConnection(); try { addTrigger(connection, t, defaultEncodingType); } catch (Exception e) { throw new TriggerLoaderException("Error uploading trigger", e); } finally { DbUtils.closeQuietly(connection); } } private synchronized void addTrigger(Connection connection, Trigger t, EncodingType encType) throws TriggerLoaderException { QueryRunner runner = new QueryRunner(); long id; try { runner.update(connection, ADD_TRIGGER, DateTime.now().getMillis()); connection.commit(); id = runner.query(connection, LastInsertID.LAST_INSERT_ID, new LastInsertID()); if (id == -1L) { logger.error("trigger id is not properly created."); throw new TriggerLoaderException("trigger id is not properly created."); } t.setTriggerId((int) id); updateTrigger(t); logger.info("uploaded trigger " + t.getDescription()); } catch (SQLException e) { throw new TriggerLoaderException("Error creating trigger.", e); } } @Override public void updateTrigger(Trigger t) throws TriggerLoaderException { if (logger.isDebugEnabled()) { logger.debug("Updating trigger " + t.getTriggerId() + " into db."); } t.setLastModifyTime(System.currentTimeMillis()); Connection connection = getConnection(); try { updateTrigger(connection, t, defaultEncodingType); } catch (Exception e) { e.printStackTrace(); throw new TriggerLoaderException("Failed to update trigger " + t.toString() + " into db!"); } finally { DbUtils.closeQuietly(connection); } } private void updateTrigger(Connection connection, Trigger t, EncodingType encType) throws TriggerLoaderException { String json = JSONUtils.toJSON(t.toJson()); byte[] data = null; try { byte[] stringData = json.getBytes("UTF-8"); data = stringData; if (encType == EncodingType.GZIP) { data = GZIPUtils.gzipBytes(stringData); } logger.debug("NumChars: " + json.length() + " UTF-8:" + stringData.length + " Gzip:" + data.length); } catch (IOException e) { throw new TriggerLoaderException("Error encoding the trigger " + t.toString()); } QueryRunner runner = new QueryRunner(); try { int updates = runner.update(connection, UPDATE_TRIGGER, t.getSource(), t.getLastModifyTime(), encType.getNumVal(), data, t.getTriggerId()); connection.commit(); if (updates == 0) { throw new TriggerLoaderException("No trigger has been updated."); } else { if (logger.isDebugEnabled()) { logger.debug("Updated " + updates + " records."); } } } catch (SQLException e) { logger.error(UPDATE_TRIGGER + " failed."); throw new TriggerLoaderException("Update trigger " + t.toString() + " into db failed. ", e); } } private static class LastInsertID implements ResultSetHandler<Long> { private static String LAST_INSERT_ID = "SELECT LAST_INSERT_ID()"; @Override public Long handle(ResultSet rs) throws SQLException { if (!rs.next()) { return -1L; } long id = rs.getLong(1); return id; } } public static class TriggerResultHandler implements ResultSetHandler<List<Trigger>> { @Override public List<Trigger> handle(ResultSet rs) throws SQLException { if (!rs.next()) { return Collections.<Trigger> emptyList(); } ArrayList<Trigger> triggers = new ArrayList<Trigger>(); do { int triggerId = rs.getInt(1); int encodingType = rs.getInt(4); byte[] data = rs.getBytes(5); Object jsonObj = null; if (data != null) { EncodingType encType = EncodingType.fromInteger(encodingType); try { // Convoluted way to inflate strings. Should find common package or // helper function. if (encType == EncodingType.GZIP) { // Decompress the sucker. String jsonString = GZIPUtils.unGzipString(data, "UTF-8"); jsonObj = JSONUtils.parseJSONFromString(jsonString); } else { String jsonString = new String(data, "UTF-8"); jsonObj = JSONUtils.parseJSONFromString(jsonString); } } catch (IOException e) { throw new SQLException("Error reconstructing trigger data "); } } Trigger t = null; try { t = Trigger.fromJson(jsonObj); triggers.add(t); } catch (Exception e) { e.printStackTrace(); logger.error("Failed to load trigger " + triggerId); } } while (rs.next()); return triggers; } } private Connection getConnection() throws TriggerLoaderException { Connection connection = null; try { connection = super.getDBConnection(false); } catch (Exception e) { DbUtils.closeQuietly(connection); throw new TriggerLoaderException("Error getting DB connection.", e); } return connection; } @Override public Trigger loadTrigger(int triggerId) throws TriggerLoaderException { logger.info("Loading trigger " + triggerId + " from db."); Connection connection = getConnection(); QueryRunner runner = new QueryRunner(); ResultSetHandler<List<Trigger>> handler = new TriggerResultHandler(); List<Trigger> triggers; try { triggers = runner.query(connection, GET_TRIGGER, handler, triggerId); } catch (SQLException e) { logger.error(GET_TRIGGER + " failed."); throw new TriggerLoaderException("Loading trigger from db failed. ", e); } finally { DbUtils.closeQuietly(connection); } if (triggers.size() == 0) { logger.error("Loaded 0 triggers. Failed to load trigger " + triggerId); throw new TriggerLoaderException( "Loaded 0 triggers. Failed to load trigger " + triggerId); } return triggers.get(0); } }