package org.epics.archiverappliance.config.persistence; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.LinkedList; import java.util.List; import javax.naming.Context; import javax.naming.InitialContext; import javax.sql.DataSource; import org.apache.log4j.Logger; import org.epics.archiverappliance.config.ConfigPersistence; import org.epics.archiverappliance.config.PVTypeInfo; import org.epics.archiverappliance.config.UserSpecifiedSamplingParams; import org.epics.archiverappliance.config.exception.ConfigException; import org.epics.archiverappliance.utils.ui.JSONDecoder; import org.epics.archiverappliance.utils.ui.JSONEncoder; import org.json.simple.JSONObject; import org.json.simple.JSONValue; /** * Persistence layer ontop of MySQL TEXT(blobs) on InnoDB * Bear in mind, an untuned mysql server does poorly in terms of performance. * At the minimum consider using innodb_flush_log_at_trx_commit=0 if you plan to import large numbers of pvs etc. * @author mshankar * */ public class MySQLPersistence implements ConfigPersistence { private static Logger configlogger = Logger.getLogger("config." + MySQLPersistence.class.getName()); private static Logger logger = Logger.getLogger(MySQLPersistence.class.getName()); private DataSource theDataSource; public MySQLPersistence() throws ConfigException { try { configlogger.info("Looking up datasource called jdbc/archappl in the java:/comp/env namespace using JDNI"); Context initContext = new InitialContext(); Context envContext = (Context) initContext.lookup("java:/comp/env"); theDataSource = (DataSource)envContext.lookup("jdbc/archappl"); configlogger.info("Found datasource called jdbc/archappl in the java:/comp/env namespace using JDNI"); } catch(Exception ex) { throw new ConfigException("Exception initializing MySQLPersistence ", ex); } } @Override public List<String> getTypeInfoKeys() throws IOException { return getKeys("SELECT pvName AS pvName FROM PVTypeInfo ORDER BY pvName;", "getTypeInfoKeys"); } @Override public PVTypeInfo getTypeInfo(String pvName) throws IOException { return getValueForKey("SELECT typeInfoJSON AS typeInfoJSON FROM PVTypeInfo WHERE pvName = ?;", pvName, new PVTypeInfo(), PVTypeInfo.class, "getTypeInfo"); } @Override public void putTypeInfo(String pvName, PVTypeInfo typeInfo) throws IOException { putValueForKey("INSERT INTO PVTypeInfo (pvName, typeInfoJSON) VALUES (?, ?) ON DUPLICATE KEY UPDATE typeInfoJSON = ?;", pvName, typeInfo, PVTypeInfo.class, "putTypeInfo"); } @Override public void deleteTypeInfo(String pvName) throws IOException { removeKey("DELETE FROM PVTypeInfo WHERE pvName = ?;", pvName, "deleteTypeInfo"); } @Override public List<String> getArchivePVRequestsKeys() throws IOException { return getKeys("SELECT pvName AS pvName FROM ArchivePVRequests ORDER BY pvName;", "getArchivePVRequestsKeys"); } @Override public UserSpecifiedSamplingParams getArchivePVRequest(String pvName) throws IOException { return getValueForKey("SELECT userParams AS userParams FROM ArchivePVRequests WHERE pvName = ?;", pvName, new UserSpecifiedSamplingParams(), UserSpecifiedSamplingParams.class, "getArchivePVRequest"); } @Override public void putArchivePVRequest(String pvName, UserSpecifiedSamplingParams userParams) throws IOException { putValueForKey("INSERT INTO ArchivePVRequests (pvName, userParams) VALUES (?, ?) ON DUPLICATE KEY UPDATE userParams = ?;", pvName, userParams, UserSpecifiedSamplingParams.class, "putArchivePVRequest"); } @Override public void removeArchivePVRequest(String pvName) throws IOException { removeKey("DELETE FROM ArchivePVRequests WHERE pvName = ?;", pvName, "removeArchivePVRequest"); } @Override public List<String> getExternalDataServersKeys() throws IOException { return getKeys("SELECT serverid AS serverid FROM ExternalDataServers ORDER BY serverid;", "getExternalDataServersKeys"); } @Override public String getExternalDataServer(String serverId) throws IOException { return getStringValueForKey("SELECT serverinfo AS serverinfo FROM ExternalDataServers WHERE serverid = ?;", serverId, "getExternalDataServer"); } @Override public void putExternalDataServer(String serverId, String serverInfo) throws IOException { putStringValueForKey("INSERT INTO ExternalDataServers (serverid, serverinfo) VALUES (?, ?) ON DUPLICATE KEY UPDATE serverinfo = ?;", serverId, serverInfo, "putExternalDataServer"); } @Override public void removeExternalDataServer(String serverId, String serverInfo) throws IOException { removeKey("DELETE FROM ExternalDataServers WHERE serverid = ?;", serverId, "removeExternalDataServer"); } @Override public List<String> getAliasNamesToRealNamesKeys() throws IOException { return getKeys("SELECT pvName AS pvName FROM PVAliases ORDER BY pvName;", "getAliasNamesToRealNamesKeys"); } @Override public String getAliasNamesToRealName(String pvName) throws IOException { return getStringValueForKey("SELECT realName AS realName FROM PVAliases WHERE pvName = ?;", pvName, "getAliasNamesToRealName"); } @Override public void putAliasNamesToRealName(String pvName, String realName) throws IOException { putStringValueForKey("INSERT INTO PVAliases (pvName, realName) VALUES (?, ?) ON DUPLICATE KEY UPDATE realName = ?;", pvName, realName, "putAliasNamesToRealName"); } @Override public void removeAliasName(String pvName, String realName) throws IOException { removeKey("DELETE FROM PVAliases WHERE pvName = ?;", pvName, "removeAliasName"); } private List<String> getKeys(String sql, String msg) throws IOException { LinkedList<String> ret = new LinkedList<String>(); try(Connection conn = theDataSource.getConnection()) { try(PreparedStatement stmt = conn.prepareStatement(sql)) { try(ResultSet rs = stmt.executeQuery()) { while(rs.next()) { String pvName = rs.getString(1); ret.add(pvName); } } } } catch(SQLException ex) { throw new IOException(ex); } logger.debug(msg + " returns " + ret.size() + " keys"); return ret; } private <T> T getValueForKey(String sql, String key, T obj, Class<T> clazz, String msg) throws IOException { if(key == null || key.equals("")) return null; try(Connection conn = theDataSource.getConnection()) { try(PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setString(1, key); try(ResultSet rs = stmt.executeQuery()) { while(rs.next()) { String jsonStr = rs.getString(1); JSONObject jsonObj = (JSONObject) JSONValue.parse(jsonStr); JSONDecoder<T> decoder = JSONDecoder.getDecoder(clazz); decoder.decode(jsonObj, obj); return obj; } } } } catch(Exception ex) { throw new IOException(ex); } return null; } private String getStringValueForKey(String sql, String key, String msg) throws IOException { if(key == null || key.equals("")) return null; try(Connection conn = theDataSource.getConnection()) { try(PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setString(1, key); try(ResultSet rs = stmt.executeQuery()) { while(rs.next()) { return rs.getString(1); } } } } catch(Exception ex) { throw new IOException(ex); } return null; } private <T> void putValueForKey(String sql, String key, T obj, Class<T> clazz, String msg) throws IOException { if(key == null || key.equals("")) throw new IOException("key cannot be null when persisting " + msg); if(obj == null || obj.equals("")) throw new IOException("value cannot be null when persisting " + msg); try(Connection conn = theDataSource.getConnection()) { JSONEncoder<T> encoder = JSONEncoder.getEncoder(clazz); JSONObject jsonObj = encoder.encode(obj); String jsonStr = jsonObj.toJSONString(); try(PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setString(1, key); stmt.setString(2, jsonStr); stmt.setString(3, jsonStr); int rowsChanged = stmt.executeUpdate(); if(rowsChanged != 1) { logger.warn(rowsChanged + " rows changed when updating key " + key + " in " + msg); } else { logger.debug("Successfully updated value for key " + key + " in " + msg); } } } catch(Exception ex) { throw new IOException(ex); } } private void putStringValueForKey(String sql, String key, String value, String msg) throws IOException { if(key == null || key.equals("")) throw new IOException("key cannot be null when persisting " + msg); if(value == null || value.equals("")) throw new IOException("value cannot be null when persisting " + msg); try(Connection conn = theDataSource.getConnection()) { try(PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setString(1, key); stmt.setString(2, value); stmt.setString(3, value); int rowsChanged = stmt.executeUpdate(); if(rowsChanged != 1) { logger.warn(rowsChanged + " rows changed when updating key " + key + " in " + msg); } else { logger.debug("Successfully updated value for key " + key + " in " + msg); } } } catch(Exception ex) { throw new IOException(ex); } } private void removeKey(String sql, String key, String msg) throws IOException { try(Connection conn = theDataSource.getConnection()) { try(PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setString(1, key); int rowsChanged = stmt.executeUpdate(); if(rowsChanged != 1) { logger.warn(rowsChanged + " rows changed when removing key " + key + " in " + msg); } else { logger.debug("Successfully removed key " + key + " in " + msg); } } } catch(SQLException ex) { throw new IOException(ex); } } }