package org.curiosity.publish; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import org.apache.commons.lang.StringEscapeUtils; import org.curiosity.concept.Camera; import org.curiosity.concept.Image; import org.curiosity.concept.RoverLocation; import org.curiosity.util.DatabaseInvariants; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; /** * A {@link Publisher} that publishes to a MySql database. * * @author jherwitz */ public class MySqlPublisher implements Publisher { private final Connection conn; public MySqlPublisher(Connection conn) { this.conn = Preconditions.checkNotNull(conn); } @Override public void publishImages(List<Image> images) { Multimap<Camera, Image> imagesByCamera = HashMultimap.create(); images.forEach(image -> imagesByCamera.put(image.origin(), image)); imagesByCamera.keySet().parallelStream().forEach(camera -> { String tableName = DatabaseInvariants.imageTableName(camera); String sql = sqlFor(imagesByCamera.get(camera), camera); try { put(sql, tableName); } catch (SQLException e) { System.err.println("Error encountered while putting to " + tableName + ".\n" + Arrays.toString(e.getStackTrace())); } }); } /** * Generate the sql code for image insertion. */ private String sqlFor(Collection<Image> images, Camera camera) { // generate tuples to update List<String> tuples = images.stream().map(image -> { if (image.origin() != camera) { System.err.println(String.format("Unexpected camera type. Expected:%s Got:%s Image:%s", camera, image.origin(), image)); return null; } return String.format("('%s', '%s', '%s')", image.timestamp().getTime(), StringEscapeUtils.escapeSql(image.imageUrl().toExternalForm()), image.sol()); }).filter(p -> p != null).collect(Collectors.toList()); StringBuilder builder = new StringBuilder(); builder.append(String.format( "INSERT INTO %s.%s (timestamp, imageUrl, sol)", DatabaseInvariants.databaseName(), DatabaseInvariants.imageTableName(camera))) .append("\n"); builder.append("VALUES ").append(Joiner.on(",").join(tuples)).append("\n"); builder.append("ON DUPLICATE KEY UPDATE").append("\n"); builder.append("timestamp = VALUES(timestamp)").append(",\n"); builder.append("imageUrl = VALUES(imageUrl)").append(",\n"); builder.append("sol = VALUES(sol)").append(";\n"); return builder.toString(); } @Override public void publishLocations(List<RoverLocation> locations) { String sql = sqlFor(locations); try { put(sql, DatabaseInvariants.locationTableName()); } catch (SQLException e) { System.err.println("Error encountered while putting to " + DatabaseInvariants.locationTableName() + ".\n" + Arrays.toString(e.getStackTrace())); } } /** * Generate the sql code for location insertion. */ private String sqlFor(List<RoverLocation> locations) { List<String> tuples = locations.stream() .map(location -> String.format("('%s', '%s', '%s', '%s')", location.sol(), location.latitude(), location.longitude(), location.timestamp())) .collect(Collectors.toList()); StringBuilder builder = new StringBuilder(); builder.append(String.format( "INSERT INTO %s.%s (sol, lat, lng, timestamp)", DatabaseInvariants.databaseName(), DatabaseInvariants.locationTableName())) .append("\n"); builder.append("VALUES ").append(Joiner.on(",").join(tuples)).append("\n"); builder.append("ON DUPLICATE KEY UPDATE").append("\n"); builder.append("sol = VALUES(sol)").append(",\n"); builder.append("lat = VALUES(lat)").append(",\n"); builder.append("lng = VALUES(lng)").append(",\n"); builder.append("timestamp = VALUES(timestamp)").append(";\n"); return builder.toString(); } private void put(String sql, String table) throws SQLException { try (PreparedStatement statement = conn.prepareStatement(sql)) { int modifiedRows = statement.executeUpdate(); System.out.println("Updated " + modifiedRows + "rows in table " + table); conn.commit(); } } }