Java tutorial
/* * (C) Copyright 2014,2016 Hewlett Packard Enterprise Development Company 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.api.infrastructure.persistence.mysql; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.ArrayList; import java.sql.SQLException; import java.sql.ResultSet; import javax.inject.Inject; import javax.inject.Named; import org.skife.jdbi.v2.DBI; import org.skife.jdbi.v2.Handle; import org.skife.jdbi.v2.Query; import org.skife.jdbi.v2.tweak.ResultSetMapper; import org.skife.jdbi.v2.StatementContext; import com.google.common.base.Joiner; import com.google.common.collect.Iterables; import monasca.api.infrastructure.persistence.PersistUtils; import monasca.common.model.alarm.AggregateFunction; import monasca.common.model.alarm.AlarmOperator; import monasca.common.model.alarm.AlarmSeverity; import monasca.common.model.alarm.AlarmState; import monasca.common.model.alarm.AlarmSubExpression; import monasca.common.model.metric.MetricDefinition; import monasca.common.util.Conversions; import monasca.api.domain.exception.EntityNotFoundException; import monasca.api.domain.model.alarmdefinition.AlarmDefinition; import monasca.api.domain.model.alarmdefinition.AlarmDefinitionRepo; import monasca.api.infrastructure.persistence.DimensionQueries; import monasca.api.infrastructure.persistence.SubAlarmDefinitionQueries; import com.google.common.base.Splitter; import com.google.common.collect.Lists; /** * Alarm repository implementation. */ public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo { private static final Joiner COMMA_JOINER = Joiner.on(','); private static final String SUB_ALARM_SQL = "select sa.*, sad.dimensions from sub_alarm_definition as sa " + "left join (select sub_alarm_definition_id, group_concat(dimension_name, '=', value) as dimensions from sub_alarm_definition_dimension group by sub_alarm_definition_id ) as sad " + "on sad.sub_alarm_definition_id = sa.id where sa.alarm_definition_id = :alarmDefId"; private static final String CREATE_SUB_EXPRESSION_SQL = "insert into sub_alarm_definition " + "(id, alarm_definition_id, function, metric_name, " + "operator, threshold, period, periods, is_deterministic, " + "created_at, updated_at) " + "values (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())"; private static final String UPDATE_SUB_ALARM_DEF_SQL = "update sub_alarm_definition set " + "operator = ?, threshold = ?, is_deterministic = ?, updated_at = NOW() where id = ?"; private final DBI db; private final PersistUtils persistUtils; @Inject public AlarmDefinitionMySqlRepoImpl(@Named("mysql") DBI db, PersistUtils persistUtils) { this.db = db; this.persistUtils = persistUtils; } @Override public AlarmDefinition create(String tenantId, String id, String name, String description, String severity, String expression, Map<String, AlarmSubExpression> subExpressions, List<String> matchBy, List<String> alarmActions, List<String> okActions, List<String> undeterminedActions) { Handle h = db.open(); try { h.begin(); h.insert( "insert into alarm_definition (id, tenant_id, name, description, severity, expression, match_by, actions_enabled, created_at, updated_at, deleted_at) values (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW(), NULL)", id, tenantId, name, description, severity, expression, matchBy == null || Iterables.isEmpty(matchBy) ? null : COMMA_JOINER.join(matchBy), true); // Persist sub-alarms createSubExpressions(h, id, subExpressions); // Persist actions persistActions(h, id, AlarmState.ALARM, alarmActions); persistActions(h, id, AlarmState.OK, okActions); persistActions(h, id, AlarmState.UNDETERMINED, undeterminedActions); h.commit(); return new AlarmDefinition(id, name, description, severity, expression, matchBy, true, alarmActions, okActions == null ? Collections.<String>emptyList() : okActions, undeterminedActions == null ? Collections.<String>emptyList() : undeterminedActions); } catch (RuntimeException e) { h.rollback(); throw e; } finally { h.close(); } } @Override public void deleteById(String tenantId, String alarmDefId) { try (Handle h = db.open()) { if (h.update( "update alarm_definition set deleted_at = NOW() where tenant_id = ? and id = ? and deleted_at is NULL", tenantId, alarmDefId) == 0) throw new EntityNotFoundException("No alarm definition exists for %s", alarmDefId); // Cascade soft delete to alarms h.execute("delete from alarm where alarm_definition_id = :id", alarmDefId); } } @Override public String exists(String tenantId, String name) { try (Handle h = db.open()) { Map<String, Object> map = h.createQuery( "select id from alarm_definition where tenant_id = :tenantId and name = :name and deleted_at is NULL") .bind("tenantId", tenantId).bind("name", name).first(); if (map != null) { if (map.values().size() != 0) { return map.get("id").toString(); } else { return null; } } else { return null; } } } @SuppressWarnings("unchecked") @Override public List<AlarmDefinition> find(String tenantId, String name, Map<String, String> dimensions, List<AlarmSeverity> severities, List<String> sortBy, String offset, int limit) { try (Handle h = db.open()) { String query = " SELECT t.id, t.tenant_id, t.name, t.description, t.expression, t.severity, t.match_by," + " t.actions_enabled, t.created_at, t.updated_at, t.deleted_at, " + " GROUP_CONCAT(aa.alarm_state) AS states, " + " GROUP_CONCAT(aa.action_id) AS notificationIds " + "FROM (SELECT distinct ad.id, ad.tenant_id, ad.name, ad.description, ad.expression," + " ad.severity, ad.match_by, ad.actions_enabled, ad.created_at, " + " ad.updated_at, ad.deleted_at " + " FROM alarm_definition AS ad " + " LEFT OUTER JOIN sub_alarm_definition AS sad ON ad.id = sad.alarm_definition_id " + " LEFT OUTER JOIN sub_alarm_definition_dimension AS dim ON sad.id = dim.sub_alarm_definition_id %1$s " + " WHERE ad.tenant_id = :tenantId AND ad.deleted_at IS NULL %2$s) AS t " + "LEFT OUTER JOIN alarm_action AS aa ON t.id = aa.alarm_definition_id " + "GROUP BY t.id %3$s %4$s %5$s"; StringBuilder sbWhere = new StringBuilder(); if (name != null) { sbWhere.append(" and ad.name = :name"); } sbWhere.append(MySQLUtils.buildSeverityAndClause(severities)); String orderByPart = ""; if (sortBy != null && !sortBy.isEmpty()) { orderByPart = " order by " + COMMA_JOINER.join(sortBy); if (!orderByPart.contains("id")) { orderByPart = orderByPart + ",id"; } } else { orderByPart = " order by id "; } String limitPart = ""; if (limit > 0) { limitPart = " limit :limit"; } String offsetPart = ""; if (offset != null) { offsetPart = " offset " + offset + ' '; } String sql = String.format(query, SubAlarmDefinitionQueries.buildJoinClauseFor(dimensions), sbWhere, orderByPart, limitPart, offsetPart); Query<?> q = h.createQuery(sql); q.bind("tenantId", tenantId); if (name != null) { q.bind("name", name); } MySQLUtils.bindSeverityToQuery(q, severities); if (limit > 0) { q.bind("limit", limit + 1); } q.registerMapper(new AlarmDefinitionMapper()); q = q.mapTo(AlarmDefinition.class); SubAlarmDefinitionQueries.bindDimensionsToQuery(q, dimensions); List<AlarmDefinition> resultSet = (List<AlarmDefinition>) q.list(); return resultSet; } } @Override public AlarmDefinition findById(String tenantId, String alarmDefId) { try (Handle h = db.open()) { String query = "SELECT alarm_definition.id, alarm_definition.tenant_id, alarm_definition.name, alarm_definition.description, " + "alarm_definition.expression, alarm_definition.severity, alarm_definition.match_by, alarm_definition.actions_enabled, " + " alarm_definition.created_at, alarm_definition.updated_at, alarm_definition.deleted_at, " + "GROUP_CONCAT(alarm_action.action_id) AS notificationIds,group_concat(alarm_action.alarm_state) AS states " + "FROM alarm_definition LEFT OUTER JOIN alarm_action ON alarm_definition.id=alarm_action.alarm_definition_id " + " WHERE alarm_definition.tenant_id=:tenantId AND alarm_definition.id=:alarmDefId AND alarm_definition.deleted_at " + " IS NULL GROUP BY alarm_definition.id"; Query<?> q = h.createQuery(query); q.bind("tenantId", tenantId); q.bind("alarmDefId", alarmDefId); q.registerMapper(new AlarmDefinitionMapper()); q = q.mapTo(AlarmDefinition.class); AlarmDefinition alarmDefinition = (AlarmDefinition) q.first(); if (alarmDefinition == null) { throw new EntityNotFoundException("No alarm definition exists for %s", alarmDefId); } return alarmDefinition; } } @Override public Map<String, MetricDefinition> findSubAlarmMetricDefinitions(String alarmDefId) { try (Handle h = db.open()) { List<Map<String, Object>> rows = h.createQuery(SUB_ALARM_SQL).bind("alarmDefId", alarmDefId).list(); Map<String, MetricDefinition> subAlarmMetricDefs = new HashMap<>(); for (Map<String, Object> row : rows) { String id = (String) row.get("id"); String metricName = (String) row.get("metric_name"); Map<String, String> dimensions = DimensionQueries.dimensionsFor((String) row.get("dimensions")); subAlarmMetricDefs.put(id, new MetricDefinition(metricName, dimensions)); } return subAlarmMetricDefs; } } @Override public Map<String, AlarmSubExpression> findSubExpressions(String alarmDefId) { try (Handle h = db.open()) { List<Map<String, Object>> rows = h.createQuery(SUB_ALARM_SQL).bind("alarmDefId", alarmDefId).list(); Map<String, AlarmSubExpression> subExpressions = new HashMap<>(); for (Map<String, Object> row : rows) { String id = (String) row.get("id"); AggregateFunction function = AggregateFunction.fromJson((String) row.get("function")); String metricName = (String) row.get("metric_name"); AlarmOperator operator = AlarmOperator.fromJson((String) row.get("operator")); Double threshold = (Double) row.get("threshold"); // MySQL connector returns an Integer, Drizzle returns a Long for period and periods. // Need to convert the results appropriately based on type. Integer period = Conversions.variantToInteger(row.get("period")); Integer periods = Conversions.variantToInteger(row.get("periods")); Boolean isDeterministic = Conversions.variantToBoolean(row.get("is_deterministic")); Map<String, String> dimensions = DimensionQueries.dimensionsFor((String) row.get("dimensions")); subExpressions.put(id, new AlarmSubExpression(function, new MetricDefinition(metricName, dimensions), operator, threshold, period, periods, isDeterministic)); } return subExpressions; } } @Override public void update(String tenantId, String id, boolean patch, String name, String description, String expression, List<String> matchBy, String severity, boolean actionsEnabled, Collection<String> oldSubAlarmIds, Map<String, AlarmSubExpression> changedSubAlarms, Map<String, AlarmSubExpression> newSubAlarms, List<String> alarmActions, List<String> okActions, List<String> undeterminedActions) { Handle h = db.open(); try { h.begin(); h.insert( "update alarm_definition set name = ?, description = ?, expression = ?, match_by = ?, severity = ?, actions_enabled = ?, updated_at = NOW() where tenant_id = ? and id = ?", name, description, expression, matchBy == null || Iterables.isEmpty(matchBy) ? null : COMMA_JOINER.join(matchBy), severity, actionsEnabled, tenantId, id); // Delete old sub-alarms if (oldSubAlarmIds != null) for (String oldSubAlarmId : oldSubAlarmIds) h.execute("delete from sub_alarm_definition where id = ?", oldSubAlarmId); // Update changed sub-alarms if (changedSubAlarms != null) for (Map.Entry<String, AlarmSubExpression> entry : changedSubAlarms.entrySet()) { AlarmSubExpression sa = entry.getValue(); h.execute(UPDATE_SUB_ALARM_DEF_SQL, sa.getOperator().name(), sa.getThreshold(), sa.isDeterministic(), entry.getKey()); } // Insert new sub-alarms createSubExpressions(h, id, newSubAlarms); // Delete old actions if (patch) { deleteActions(h, id, AlarmState.ALARM, alarmActions); deleteActions(h, id, AlarmState.OK, okActions); deleteActions(h, id, AlarmState.UNDETERMINED, undeterminedActions); } else h.execute("delete from alarm_action where alarm_definition_id = ?", id); // Insert new actions persistActions(h, id, AlarmState.ALARM, alarmActions); persistActions(h, id, AlarmState.OK, okActions); persistActions(h, id, AlarmState.UNDETERMINED, undeterminedActions); h.commit(); } catch (RuntimeException e) { h.rollback(); throw e; } finally { h.close(); } } private void deleteActions(Handle handle, String id, AlarmState alarmState, List<String> actions) { if (actions != null) handle.execute("delete from alarm_action where alarm_definition_id = ? and alarm_state = ?", id, alarmState.name()); } private void persistActions(Handle handle, String id, AlarmState alarmState, List<String> actions) { if (actions != null) for (String action : actions) handle.insert("insert into alarm_action values (?, ?, ?)", id, alarmState.name(), action); } private void createSubExpressions(Handle handle, String id, Map<String, AlarmSubExpression> alarmSubExpressions) { if (alarmSubExpressions != null) { for (Map.Entry<String, AlarmSubExpression> subEntry : alarmSubExpressions.entrySet()) { String subAlarmId = subEntry.getKey(); AlarmSubExpression subExpr = subEntry.getValue(); MetricDefinition metricDef = subExpr.getMetricDefinition(); // Persist sub-alarm handle.insert(CREATE_SUB_EXPRESSION_SQL, subAlarmId, id, subExpr.getFunction().name(), metricDef.name, subExpr.getOperator().name(), subExpr.getThreshold(), subExpr.getPeriod(), subExpr.getPeriods(), subExpr.isDeterministic()); // Persist sub-alarm dimensions if (metricDef.dimensions != null && !metricDef.dimensions.isEmpty()) for (Map.Entry<String, String> dimEntry : metricDef.dimensions.entrySet()) handle.insert("insert into sub_alarm_definition_dimension values (?, ?, ?)", subAlarmId, dimEntry.getKey(), dimEntry.getValue()); } } } private static class AlarmDefinitionMapper implements ResultSetMapper<AlarmDefinition> { private static final Splitter COMMA_SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults(); public AlarmDefinition map(int index, ResultSet r, StatementContext ctx) throws SQLException { String notificationIds = r.getString("notificationIds"); String states = r.getString("states"); String matchBy = r.getString("match_by"); List<String> notifications = splitStringIntoList(notificationIds); List<String> state = splitStringIntoList(states); List<String> match = splitStringIntoList(matchBy); List<String> okActionIds = new ArrayList<String>(); List<String> alarmActionIds = new ArrayList<String>(); List<String> undeterminedActionIds = new ArrayList<String>(); int stateAndActionIndex = 0; for (String singleState : state) { if (singleState.equals(AlarmState.UNDETERMINED.name())) { undeterminedActionIds.add(notifications.get(stateAndActionIndex)); } if (singleState.equals(AlarmState.OK.name())) { okActionIds.add(notifications.get(stateAndActionIndex)); } if (singleState.equals(AlarmState.ALARM.name())) { alarmActionIds.add(notifications.get(stateAndActionIndex)); } stateAndActionIndex++; } return new AlarmDefinition(r.getString("id"), r.getString("name"), r.getString("description"), r.getString("severity"), r.getString("expression"), match, r.getBoolean("actions_enabled"), alarmActionIds, okActionIds, undeterminedActionIds); } private List<String> splitStringIntoList(String commaDelimitedString) { if (commaDelimitedString == null) { return new ArrayList<String>(); } Iterable<String> split = COMMA_SPLITTER.split(commaDelimitedString); return Lists.newArrayList(split); } } }