package org.gbif.checklistbank.service.mybatis.postgres; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Functions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.postgresql.util.HStoreConverter; /** * A mybatis type handler that translates from the typed java.util.Map<String, Integer> to the * postgres hstore database type. Any non integer values in hstore are silently ignored. * * As we do not map all java map types to this mybatis handler apply the handler manually for the relevant hstore fields * in the mapper xml, for example see DatasetMetricsMapper.xml. */ public abstract class HstoreCountTypeHandler<KEY extends Comparable> extends BaseTypeHandler<Map<KEY, Integer>> { @Override public void setNonNullParameter(PreparedStatement ps, int i, Map<KEY, Integer> parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, HStoreConverter.toString(parameter)); } @Override public Map<KEY, Integer> getNullableResult(ResultSet rs, String columnName) throws SQLException { return fromString(rs.getString(columnName)); } @Override public Map<KEY, Integer> getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return fromString(rs.getString(columnIndex)); } @Override public Map<KEY, Integer> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return fromString(cs.getString(columnIndex)); } private Map<KEY, Integer> fromString(String hstring) { HashMap<KEY, Integer> typedMap = Maps.newHashMap(); if (!Strings.isNullOrEmpty(hstring)) { Map<String, String> rawMap = HStoreConverter.fromString(hstring); for (Map.Entry<String, String> entry : rawMap.entrySet()) { try { typedMap.put(toKey(entry.getKey()), Integer.parseInt(entry.getValue())); } catch (IllegalArgumentException e) { // ignore this entry } } } return sortMap(typedMap); } /** * Can be overridden to return sorted maps in custom manners. * By default sorts the map according to the count values in descending order. */ protected Map<KEY, Integer> sortMap(HashMap<KEY, Integer> map) { return sortMapByValuesDesc(map); } @VisibleForTesting protected static <KEY extends Comparable> Map<KEY, Integer> sortMapByValuesDesc(HashMap<KEY, Integer> map) { final Ordering<KEY> reverseValuesAndNaturalKeysOrdering = Ordering.natural().reverse().nullsLast().onResultOf(Functions.forMap(map, null)) // natural for values .compound(Ordering.natural()); // secondary - natural ordering of keys return ImmutableSortedMap.copyOf(map, reverseValuesAndNaturalKeysOrdering); } protected abstract KEY toKey(String key) throws IllegalArgumentException; }