Java tutorial
/* * (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP * * 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 monasca.thresh.infrastructure.persistence; import monasca.common.model.alarm.AlarmState; import monasca.common.model.alarm.AlarmSubExpression; import monasca.common.model.metric.MetricDefinition; import monasca.thresh.domain.model.Alarm; import monasca.thresh.domain.model.MetricDefinitionAndTenantId; import monasca.thresh.domain.model.SubAlarm; import monasca.thresh.domain.model.SubExpression; import monasca.thresh.domain.service.AlarmDAO; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; import org.skife.jdbi.v2.DBI; import org.skife.jdbi.v2.Handle; import org.skife.jdbi.v2.Query; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.text.SimpleDateFormat; import java.util.TimeZone; import java.util.TreeMap; import javax.inject.Inject; /** * Alarm DAO implementation. */ public class AlarmDAOImpl implements AlarmDAO { private static final Logger logger = LoggerFactory.getLogger(AlarmDAOImpl.class); public static final int MAX_COLUMN_LENGTH = 255; private final DBI db; private final ThreadLocal<SimpleDateFormat> simpleDateFormatter; @Inject public AlarmDAOImpl(DBI db) { this.db = db; this.simpleDateFormatter = new ThreadLocal<>(); } @Override public List<Alarm> findForAlarmDefinitionId(String alarmDefinitionId) { return findAlarms("a.alarm_definition_id = :alarmDefinitionId ", "alarmDefinitionId", alarmDefinitionId); } @Override public List<Alarm> listAll() { return findAlarms("1=1"); // This is basically "true" and gets optimized out } private List<Alarm> findAlarms(final String additionalWhereClause, String... params) { try (final Handle h = db.open()) { final String ALARMS_SQL = "select a.id, a.alarm_definition_id, a.state, sa.id as sub_alarm_id, sa.expression, " + "sa.state as sub_alarm_state, sa.sub_expression_id, ad.tenant_id from alarm a " + "inner join sub_alarm sa on sa.alarm_id = a.id " + "inner join alarm_definition ad on a.alarm_definition_id = ad.id " + "where ad.deleted_at is null and %s " + "order by a.id"; final String sql = String.format(ALARMS_SQL, additionalWhereClause); final Query<Map<String, Object>> query = h.createQuery(sql); addQueryParameters(query, params); final List<Map<String, Object>> rows = query.list(); final List<Alarm> alarms = new ArrayList<>(rows.size()); List<SubAlarm> subAlarms = new ArrayList<SubAlarm>(); String prevAlarmId = null; Alarm alarm = null; final Map<String, Alarm> alarmMap = new HashMap<>(); final Map<String, String> tenantIdMap = new HashMap<>(); for (final Map<String, Object> row : rows) { final String alarmId = getString(row, "id"); if (!alarmId.equals(prevAlarmId)) { if (alarm != null) { alarm.setSubAlarms(subAlarms); } alarm = new Alarm(); alarm.setId(alarmId); alarm.setAlarmDefinitionId(getString(row, "alarm_definition_id")); alarm.setState(AlarmState.valueOf(getString(row, "state"))); subAlarms = new ArrayList<SubAlarm>(); alarms.add(alarm); alarmMap.put(alarmId, alarm); tenantIdMap.put(alarmId, getString(row, "tenant_id")); } final SubExpression subExpression = new SubExpression(getString(row, "sub_expression_id"), AlarmSubExpression.of(getString(row, "expression"))); final AlarmState subAlarmState = AlarmState.valueOf(getString(row, "sub_alarm_state")); final SubAlarm subAlarm = new SubAlarm(getString(row, "sub_alarm_id"), alarmId, subExpression, subAlarmState); subAlarms.add(subAlarm); prevAlarmId = alarmId; } if (alarm != null) { alarm.setSubAlarms(subAlarms); } if (!alarms.isEmpty()) { getAlarmedMetrics(h, alarmMap, tenantIdMap, additionalWhereClause, params); } return alarms; } } private void addQueryParameters(final Query<Map<String, Object>> query, String... params) { for (int i = 0; i < params.length;) { query.bind(params[i], params[i + 1]); i += 2; } } private void getAlarmedMetrics(Handle h, final Map<String, Alarm> alarmMap, final Map<String, String> tenantIdMap, final String additionalWhereClause, String... params) { final String baseSql = "select a.id, md.name, group_concat(mdim.name, '=', mdim.value order by mdim.name) as dimensions " + "from metric_definition as md " + "inner join metric_definition_dimensions as mdd on md.id = mdd.metric_definition_id " + "inner join alarm_metric as am on mdd.id = am.metric_definition_dimensions_id " + "inner join alarm as a on am.alarm_id = a.id " + "left join metric_dimension as mdim on mdim.dimension_set_id = mdd.metric_dimension_set_id " + "where %s " + "group by a.id, md.name, mdim.dimension_set_id " + "order by dimensions"; final String sql = String.format(baseSql, additionalWhereClause); final Query<Map<String, Object>> query = h.createQuery(sql); addQueryParameters(query, params); final List<Map<String, Object>> metricRows = query.list(); for (final Map<String, Object> row : metricRows) { final String alarmId = getString(row, "id"); final Alarm alarm = alarmMap.get(alarmId); // This shouldn't happen but it is possible an Alarm gets created after the AlarmDefinition is // marked deleted and any existing alarms are deleted but before the Threshold Engine gets the // AlarmDefinitionDeleted message if (alarm == null) { continue; } final MetricDefinition md = createMetricDefinitionFromRow(row); alarm.addAlarmedMetric(new MetricDefinitionAndTenantId(md, tenantIdMap.get(alarmId))); } } @Override public void addAlarmedMetric(String alarmId, MetricDefinitionAndTenantId metricDefinition) { Handle h = db.open(); try { h.begin(); createAlarmedMetric(h, metricDefinition, alarmId); h.commit(); } catch (RuntimeException e) { h.rollback(); throw e; } finally { h.close(); } } private void createAlarmedMetric(Handle h, MetricDefinitionAndTenantId metricDefinition, String alarmId) { final Sha1HashId metricDefinitionDimensionId = insertMetricDefinitionDimension(h, metricDefinition); h.insert("insert into alarm_metric (alarm_id, metric_definition_dimensions_id) values (?, ?)", alarmId, metricDefinitionDimensionId.getSha1Hash()); } private Sha1HashId insertMetricDefinitionDimension(Handle h, MetricDefinitionAndTenantId mdtid) { final Sha1HashId metricDefinitionId = insertMetricDefinition(h, mdtid); final Sha1HashId metricDimensionSetId = insertMetricDimensionSet(h, mdtid.metricDefinition.dimensions); final byte[] definitionDimensionsIdSha1Hash = DigestUtils .sha(metricDefinitionId.toHexString() + metricDimensionSetId.toHexString()); h.insert( "insert into metric_definition_dimensions (id, metric_definition_id, metric_dimension_set_id) values (?, ?, ?)" + "on duplicate key update id=id", definitionDimensionsIdSha1Hash, metricDefinitionId.getSha1Hash(), metricDimensionSetId.getSha1Hash()); return new Sha1HashId(definitionDimensionsIdSha1Hash); } private Sha1HashId insertMetricDimensionSet(Handle h, Map<String, String> dimensions) { final byte[] dimensionSetId = calculateDimensionSHA1(dimensions); for (final Map.Entry<String, String> entry : dimensions.entrySet()) { h.insert( "insert into metric_dimension(dimension_set_id, name, value) values (?, ?, ?) " + "on duplicate key update dimension_set_id=dimension_set_id", dimensionSetId, entry.getKey(), entry.getValue()); } return new Sha1HashId(dimensionSetId); } private byte[] calculateDimensionSHA1(final Map<String, String> dimensions) { // Calculate dimensions sha1 hash id. final StringBuilder dimensionIdStringToHash = new StringBuilder(""); if (dimensions != null) { // Sort the dimensions on name and value. TreeMap<String, String> dimensionTreeMap = new TreeMap<>(dimensions); for (String dimensionName : dimensionTreeMap.keySet()) { if (dimensionName != null && !dimensionName.isEmpty()) { String dimensionValue = dimensionTreeMap.get(dimensionName); if (dimensionValue != null && !dimensionValue.isEmpty()) { dimensionIdStringToHash.append(trunc(dimensionName, MAX_COLUMN_LENGTH)); dimensionIdStringToHash.append(trunc(dimensionValue, MAX_COLUMN_LENGTH)); } } } } final byte[] dimensionIdSha1Hash = DigestUtils.sha(dimensionIdStringToHash.toString()); return dimensionIdSha1Hash; } private Sha1HashId insertMetricDefinition(Handle h, MetricDefinitionAndTenantId mdtid) { final String region = ""; // TODO We currently don't have region final String definitionIdStringToHash = trunc(mdtid.metricDefinition.name, MAX_COLUMN_LENGTH) + trunc(mdtid.tenantId, MAX_COLUMN_LENGTH) + trunc(region, MAX_COLUMN_LENGTH); final byte[] id = DigestUtils.sha(definitionIdStringToHash); h.insert("insert into metric_definition(id, name, tenant_id) values (?, ?, ?) " + "on duplicate key update id=id", id, mdtid.metricDefinition.name, mdtid.tenantId); return new Sha1HashId(id); } @Override public void createAlarm(Alarm alarm) { Handle h = db.open(); try { String timestamp = formatDateFromMillis(System.currentTimeMillis()); h.begin(); h.insert( "insert into alarm (id, alarm_definition_id, state, state_updated_at, created_at, updated_at) values (?, ?, ?, ?, ?, ?)", alarm.getId(), alarm.getAlarmDefinitionId(), alarm.getState().toString(), timestamp, timestamp, timestamp); for (final SubAlarm subAlarm : alarm.getSubAlarms()) { h.insert( "insert into sub_alarm (id, alarm_id, sub_expression_id, expression, state, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?)", subAlarm.getId(), subAlarm.getAlarmId(), subAlarm.getAlarmSubExpressionId(), subAlarm.getExpression().getExpression(), subAlarm.getState().toString(), timestamp, timestamp); } for (final MetricDefinitionAndTenantId md : alarm.getAlarmedMetrics()) { createAlarmedMetric(h, md, alarm.getId()); } h.commit(); } catch (RuntimeException e) { h.rollback(); throw e; } finally { h.close(); } } @Override public Alarm findById(String id) { final List<Alarm> alarms = findAlarms("a.id = :alarm_id ", "alarm_id", id); if (alarms.isEmpty()) { return null; } else { return alarms.get(0); } } @Override public void updateState(String id, AlarmState state, long msTimestamp) { try (final Handle h = db.open()) { String timestamp = formatDateFromMillis(msTimestamp); h.createStatement( "update alarm set state = :state, state_updated_at = :timestamp, updated_at = :timestamp where id = :id") .bind("id", id).bind("timestamp", timestamp).bind("state", state.toString()).execute(); } } @Override public int updateSubAlarmExpressions(String alarmSubExpressionId, AlarmSubExpression alarmSubExpression) { try (final Handle h = db.open()) { return h.createStatement( "update sub_alarm set expression=:expression where sub_expression_id=:alarmSubExpressionId") .bind("expression", alarmSubExpression.getExpression()) .bind("alarmSubExpressionId", alarmSubExpressionId).execute(); } } @Override public void updateSubAlarmState(String id, AlarmState subAlarmState) { try (Handle h = db.open()) { final String timestamp = formatDateFromMillis(System.currentTimeMillis()); h.createStatement("update sub_alarm set state=:state, updated_at=:updated_at where id=:id") .bind("state", subAlarmState.toString()).bind("updated_at", timestamp).bind("id", id).execute(); } } @Override public void deleteByDefinitionId(String alarmDefinitionId) { try (Handle h = db.open()) { h.execute("delete from alarm where alarm_definition_id = :id", alarmDefinitionId); } } private String formatDateFromMillis(final long msTimestamp) { if (simpleDateFormatter.get() == null) { simpleDateFormatter.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")); simpleDateFormatter.get().setTimeZone(TimeZone.getTimeZone("GMT-0")); } return simpleDateFormatter.get().format(new Date(msTimestamp)); } private MetricDefinition createMetricDefinitionFromRow(final Map<String, Object> row) { final Map<String, String> dimensionMap = new HashMap<>(); final String dimensions = getString(row, "dimensions"); if (dimensions != null) { for (String dimension : dimensions.split(",")) { final String[] parsed_dimension = dimension.split("="); dimensionMap.put(parsed_dimension[0], parsed_dimension[1]); } } final MetricDefinition md = new MetricDefinition(getString(row, "name"), dimensionMap); return md; } private String getString(final Map<String, Object> row, String fieldName) { return (String) row.get(fieldName); } private String trunc(String s, int l) { if (s == null) { return ""; } else if (s.length() <= l) { return s; } else { String r = s.substring(0, l); logger.warn("Input string exceeded max column length. Truncating input string {} to {} chars", s, l); logger.warn("Resulting string {}", r); return r; } } /** * This class is used when a binary id needs to be used in a map. Just using a byte[] as * a key fails because they are not considered as equal because the check is == * @author craigbr * */ private static class Sha1HashId { private final byte[] sha1Hash; public Sha1HashId(byte[] sha1Hash) { this.sha1Hash = sha1Hash; } @Override public String toString() { return "Sha1HashId{" + "sha1Hash=" + Hex.encodeHexString(sha1Hash) + "}"; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Sha1HashId)) return false; Sha1HashId that = (Sha1HashId) o; if (!Arrays.equals(sha1Hash, that.sha1Hash)) return false; return true; } @Override public int hashCode() { return Arrays.hashCode(sha1Hash); } public byte[] getSha1Hash() { return sha1Hash; } public String toHexString() { return Hex.encodeHexString(sha1Hash); } } }