Java tutorial
/* * Copyright 2016 FUJITSU LIMITED * (C) Copyright 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.hibernate; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; import javax.inject.Inject; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.apache.commons.codec.digest.DigestUtils; import org.hibernate.Criteria; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.StatelessSession; import org.hibernate.Transaction; import org.hibernate.criterion.Order; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import monasca.common.hibernate.db.AlarmDb; import monasca.common.hibernate.db.AlarmDefinitionDb; import monasca.common.hibernate.db.AlarmMetricDb; import monasca.common.hibernate.db.MetricDefinitionDb; import monasca.common.hibernate.db.MetricDefinitionDimensionsDb; import monasca.common.hibernate.db.MetricDimensionDb; import monasca.common.hibernate.db.SubAlarmDb; import monasca.common.hibernate.db.SubAlarmDefinitionDb; import monasca.common.hibernate.type.BinaryId; 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; /** * AlarmDAO hibernate implementation. * * @author lukasz.zajaczkowski@ts.fujitsu.com * @author tomasz.trebski@ts.fujitsu.com */ public class AlarmSqlImpl implements AlarmDAO { private static final Logger LOGGER = LoggerFactory.getLogger(AlarmSqlImpl.class); private static final int ALARM_ID = 0; private static final int ALARM_DEFINITION_ID = 1; private static final int ALARM_STATE = 2; private static final int SUB_ALARM_ID = 3; private static final int ALARM_EXPRESSION = 4; private static final int SUB_EXPRESSION_ID = 5; private static final int TENANT_ID = 6; private static final int MAX_COLUMN_LENGTH = 255; private final SessionFactory sessionFactory; @Inject public AlarmSqlImpl(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } @Override public Alarm findById(final String id) { final List<Alarm> alarms = this.findAlarms(new LookupHelper() { @Override public Criteria apply(@Nonnull final Criteria input) { return input.add(Restrictions.eq("a.id", id)); } @Override public Query apply(@Nonnull final Query query) { return query.setParameter("alarmId", id); } @Override public String formatHQL(@Nonnull final String hqlQuery) { return String.format(hqlQuery, "a.id=:alarmId"); } }); return alarms.isEmpty() ? null : alarms.get(0); } @Override public List<Alarm> findForAlarmDefinitionId(final String alarmDefinitionId) { return this.findAlarms(new LookupHelper() { @Override public Criteria apply(@Nonnull final Criteria input) { return input.add(Restrictions.eq("a.alarmDefinition.id", alarmDefinitionId)); } @Override public Query apply(@Nonnull final Query query) { return query.setParameter("alarmDefinitionId", alarmDefinitionId); } @Override public String formatHQL(@Nonnull final String hqlQuery) { return String.format(hqlQuery, "a.alarmDefinition.id=:alarmDefinitionId"); } }); } @Override public List<Alarm> listAll() { return this.findAlarms(LookupHelper.NOOP_HELPER); } @Override public void updateState(String id, AlarmState state, long msTimestamp) { Transaction tx = null; Session session = null; try { session = sessionFactory.openSession(); tx = session.beginTransaction(); final DateTime dt = new DateTime(msTimestamp); final AlarmDb alarm = (AlarmDb) session.get(AlarmDb.class, id); alarm.setState(state); alarm.setUpdatedAt(dt); alarm.setStateUpdatedAt(dt); session.update(alarm); tx.commit(); tx = null; } finally { this.rollbackIfNotNull(tx); if (session != null) { session.close(); } } } @Override public void addAlarmedMetric(String id, MetricDefinitionAndTenantId metricDefinition) { Transaction tx = null; Session session = null; try { session = sessionFactory.openSession(); tx = session.beginTransaction(); this.createAlarmedMetric(session, metricDefinition, id); tx.commit(); tx = null; } finally { this.rollbackIfNotNull(tx); if (session != null) { session.close(); } } } @Override @SuppressWarnings("unchecked") public void createAlarm(Alarm newAlarm) { Transaction tx = null; Session session = null; try { session = sessionFactory.openSession(); tx = session.beginTransaction(); final DateTime now = DateTime.now(); final AlarmDb alarm = new AlarmDb(newAlarm.getId(), session.get(AlarmDefinitionDb.class, newAlarm.getAlarmDefinitionId()), newAlarm.getState(), null, null, now, now, now); session.save(alarm); for (final SubAlarm subAlarm : newAlarm.getSubAlarms()) { session.save(new SubAlarmDb().setAlarm(alarm) .setSubExpression( session.get(SubAlarmDefinitionDb.class, subAlarm.getAlarmSubExpressionId())) .setExpression(subAlarm.getExpression().getExpression()).setState(subAlarm.getState()) .setUpdatedAt(now).setCreatedAt(now).setId(subAlarm.getId())); } for (final MetricDefinitionAndTenantId md : newAlarm.getAlarmedMetrics()) { this.createAlarmedMetric(session, md, newAlarm.getId()); } tx.commit(); tx = null; } finally { this.rollbackIfNotNull(tx); if (session != null) { session.close(); } } } @Override public int updateSubAlarmExpressions(String alarmSubExpressionId, AlarmSubExpression alarmSubExpression) { Transaction tx = null; Session session = null; int updatedItems; try { session = sessionFactory.openSession(); tx = session.beginTransaction(); updatedItems = session.getNamedQuery(SubAlarmDb.Queries.UPDATE_EXPRESSION_BY_SUBEXPRESSION_ID) .setString("expression", alarmSubExpression.getExpression()) .setString("alarmSubExpressionId", alarmSubExpressionId).executeUpdate(); tx.commit(); tx = null; return updatedItems; } finally { this.rollbackIfNotNull(tx); if (session != null) { session.close(); } } } @Override public void updateSubAlarmState(String subAlarmId, AlarmState subAlarmState) { Transaction tx = null; Session session = null; try { session = sessionFactory.openSession(); tx = session.beginTransaction(); final DateTime now = DateTime.now(); final SubAlarmDb subAlarm = (SubAlarmDb) session.get(SubAlarmDb.class, subAlarmId); subAlarm.setState(subAlarmState); subAlarm.setUpdatedAt(now); session.update(subAlarm); tx.commit(); tx = null; } finally { this.rollbackIfNotNull(tx); if (session != null) { session.close(); } } } @Override public void deleteByDefinitionId(final String alarmDefinitionId) { Transaction tx = null; Session session = null; try { session = sessionFactory.openSession(); tx = session.beginTransaction(); session.getNamedQuery(AlarmDb.Queries.DELETE_BY_ALARMDEFINITION_ID) .setString("alarmDefinitionId", alarmDefinitionId).executeUpdate(); tx.commit(); tx = null; } finally { this.rollbackIfNotNull(tx); if (session != null) { session.close(); } } } @SuppressWarnings("unchecked") private List<Alarm> findAlarms(@Nonnull final LookupHelper lookupHelper) { StatelessSession session = null; try { session = sessionFactory.openStatelessSession(); final Criteria criteria = lookupHelper.apply(session.createCriteria(AlarmDb.class, "a") .createAlias("a.subAlarms", "sa").createAlias("a.alarmDefinition", "ad") .add(Restrictions.isNull("ad.deletedAt")).addOrder(Order.asc("a.id")) .setProjection(Projections.projectionList().add(Projections.property("a.id")) .add(Projections.property("a.alarmDefinition.id")).add(Projections.property("a.state")) .add(Projections.alias(Projections.property("sa.id"), "sub_alarm_id")) .add(Projections.property("sa.expression")) .add(Projections.property("sa.subExpression.id")) .add(Projections.property("ad.tenantId"))) .setReadOnly(true)); assert criteria != null; return this.createAlarms(session, (List<Object[]>) criteria.list(), lookupHelper); } finally { if (session != null) { session.close(); } } } private Map<String, List<MetricDefinition>> getAlarmedMetrics(List<Object[]> alarmList) { Map<String, List<MetricDefinition>> result = Maps.newHashMap(); Map<BinaryId, List<MetricDefinition>> metricDefinitionList = Maps.newHashMap(); Map<BinaryId, Map<String, Map<String, String>>> metricList = Maps.newHashMap(); Map<String, Set<BinaryId>> mapAssociationIds = Maps.newHashMap(); for (Object[] alarmRow : alarmList) { String alarmId = (String) alarmRow[0]; String metric_name = (String) alarmRow[1]; String dimension_name = (String) alarmRow[2]; String dimension_value = (String) alarmRow[3]; BinaryId dimensionSetId = (BinaryId) alarmRow[4]; if (!metricList.containsKey(dimensionSetId)) { metricList.put(dimensionSetId, new HashMap<String, Map<String, String>>()); } Map<String, Map<String, String>> dimensions = metricList.get(dimensionSetId); if (!dimensions.containsKey(metric_name)) { dimensions.put(metric_name, new HashMap<String, String>()); } if (!mapAssociationIds.containsKey(alarmId)) { mapAssociationIds.put(alarmId, new HashSet<BinaryId>()); } mapAssociationIds.get(alarmId).add(dimensionSetId); dimensions.get(metric_name).put(dimension_name, dimension_value); } for (BinaryId keyDimensionSetId : metricList.keySet()) { Map<String, Map<String, String>> metrics = metricList.get(keyDimensionSetId); List<MetricDefinition> valueList = Lists.newArrayListWithExpectedSize(metrics.size()); for (String keyMetricName : metrics.keySet()) { MetricDefinition md = new MetricDefinition(keyMetricName, metrics.get(keyMetricName)); valueList.add(md); } metricDefinitionList.put(keyDimensionSetId, valueList); } for (String keyAlarmId : mapAssociationIds.keySet()) { if (!result.containsKey(keyAlarmId)) { result.put(keyAlarmId, new LinkedList<MetricDefinition>()); } Set<BinaryId> setDimensionId = mapAssociationIds.get(keyAlarmId); for (BinaryId keyDimensionId : setDimensionId) { List<MetricDefinition> metricDefList = metricDefinitionList.get(keyDimensionId); result.get(keyAlarmId).addAll(metricDefList); } } return result; } private List<Alarm> createAlarms(final StatelessSession session, final List<Object[]> alarmList, final LookupHelper lookupHelper) { final List<Alarm> alarms = Lists.newArrayListWithCapacity(alarmList.size()); if (alarmList.isEmpty()) { return alarms; } List<SubAlarm> subAlarms = null; String prevAlarmId = null; Alarm alarm = null; final Map<String, Alarm> alarmMap = Maps.newHashMapWithExpectedSize(alarmList.size()); final Map<String, String> tenantIdMap = Maps.newHashMapWithExpectedSize(alarmList.size()); for (Object[] alarmRow : alarmList) { final String alarmId = (String) alarmRow[ALARM_ID]; if (!alarmId.equals(prevAlarmId)) { if (alarm != null) { alarm.setSubAlarms(subAlarms); } alarm = new Alarm(); alarm.setId(alarmId); alarm.setAlarmDefinitionId((String) alarmRow[ALARM_DEFINITION_ID]); alarm.setState((AlarmState) alarmRow[ALARM_STATE]); subAlarms = Lists.newArrayListWithExpectedSize(alarmList.size()); alarms.add(alarm); alarmMap.put(alarmId, alarm); tenantIdMap.put(alarmId, (String) alarmRow[TENANT_ID]); } subAlarms.add(new SubAlarm((String) alarmRow[SUB_ALARM_ID], alarmId, new SubExpression((String) alarmRow[SUB_EXPRESSION_ID], AlarmSubExpression.of((String) alarmRow[ALARM_EXPRESSION])))); prevAlarmId = alarmId; } if (alarm != null) { alarm.setSubAlarms(subAlarms); } if (!alarms.isEmpty()) { this.getAlarmedMetrics(session, alarmMap, tenantIdMap, lookupHelper); } return alarms; } @SuppressWarnings("unchecked") private void getAlarmedMetrics(final StatelessSession session, final Map<String, Alarm> alarmMap, final Map<String, String> tenantIdMap, final LookupHelper binder) { String rawHQLQuery = "select a.id, md.name as metric_def_name, mdg.id.name, mdg.value, mdg.id.dimensionSetId from MetricDefinitionDb as md, " + "MetricDefinitionDimensionsDb as mdd, " + "AlarmMetricDb as am, " + "AlarmDb as a, " + "MetricDimensionDb as mdg where md.id = mdd.metricDefinition.id and mdd.id = am.alarmMetricId.metricDefinitionDimensions.id and " + "am.alarmMetricId.alarm.id = a.id and mdg.id.dimensionSetId = mdd.metricDimensionSetId and %s"; final Query query = binder.apply(session.createQuery(binder.formatHQL(rawHQLQuery))); final List<Object[]> metricRows = query.list(); final HashSet<String> existingAlarmId = Sets.newHashSet(); final Map<String, List<MetricDefinition>> alarmMetrics = this.getAlarmedMetrics(metricRows); for (final Object[] row : metricRows) { final String alarmId = (String) row[ALARM_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; } if (!existingAlarmId.contains(alarmId)) { List<MetricDefinition> mdList = alarmMetrics.get(alarmId); for (MetricDefinition md : mdList) { alarm.addAlarmedMetric(new MetricDefinitionAndTenantId(md, tenantIdMap.get(alarmId))); } } existingAlarmId.add(alarmId); } } private AlarmMetricDb createAlarmedMetric(final Session session, final MetricDefinitionAndTenantId metricDefinition, final String alarmId) { final MetricDefinitionDimensionsDb metricDefinitionDimension = this.insertMetricDefinitionDimension(session, metricDefinition); final AlarmDb alarm = (AlarmDb) session.load(AlarmDb.class, alarmId); final AlarmMetricDb alarmMetric = new AlarmMetricDb(alarm, metricDefinitionDimension); session.save(alarmMetric); return alarmMetric; } private MetricDefinitionDimensionsDb insertMetricDefinitionDimension(final Session session, final MetricDefinitionAndTenantId mdtId) { final MetricDefinitionDb metricDefinition = this.insertMetricDefinition(session, mdtId); final BinaryId metricDimensionSetId = this.insertMetricDimensionSet(session, mdtId.metricDefinition.dimensions); final byte[] definitionDimensionsIdSha1Hash = DigestUtils .sha(metricDefinition.getId().toHexString() + metricDimensionSetId.toHexString()); final MetricDefinitionDimensionsDb metricDefinitionDimensions = new MetricDefinitionDimensionsDb( definitionDimensionsIdSha1Hash, metricDefinition, metricDimensionSetId); return (MetricDefinitionDimensionsDb) session.merge(metricDefinitionDimensions); } private BinaryId insertMetricDimensionSet(Session session, Map<String, String> dimensions) { final byte[] dimensionSetId = calculateDimensionSHA1(dimensions); for (final Map.Entry<String, String> entry : dimensions.entrySet()) { final MetricDimensionDb metricDimension = new MetricDimensionDb(dimensionSetId, entry.getKey(), entry.getValue()); if (session.get(MetricDimensionDb.class, metricDimension.getId()) == null) { session.merge(metricDimension); } } return new BinaryId(dimensionSetId); } private MetricDefinitionDb insertMetricDefinition(final Session session, final MetricDefinitionAndTenantId mdtid) { final String region = ""; // TODO We currently don't have region final String definitionIdStringToHash = truncateString(mdtid.metricDefinition.name, MAX_COLUMN_LENGTH) + truncateString(mdtid.tenantId, MAX_COLUMN_LENGTH) + truncateString(region, MAX_COLUMN_LENGTH); final byte[] id = DigestUtils.sha(definitionIdStringToHash); final MetricDefinitionDb metricDefinition = new MetricDefinitionDb(id, mdtid.metricDefinition.name, mdtid.tenantId, region); if (session.get(MetricDefinitionDb.class, metricDefinition.getId()) == null) { session.persist(metricDefinition); return metricDefinition; } session.merge(metricDefinition); return metricDefinition; } private String truncateString(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; } } private byte[] calculateDimensionSHA1(final Map<String, String> dimensions) { // Calculate dimensions sha1 hash id. final StringBuilder dimensionIdStringToHash = new StringBuilder(""); if (dimensions != null && !dimensions.isEmpty()) { // Sort the dimensions on name and value. final Map<String, String> dimensionTreeMap = Maps.newTreeMap(ImmutableSortedMap.copyOf(dimensions)); for (final String dimensionName : dimensionTreeMap.keySet()) { if (dimensionName != null && !dimensionName.isEmpty()) { final String dimensionValue = dimensionTreeMap.get(dimensionName); if (dimensionValue != null && !dimensionValue.isEmpty()) { dimensionIdStringToHash.append(this.truncateString(dimensionName, MAX_COLUMN_LENGTH)) .append(this.truncateString(dimensionValue, MAX_COLUMN_LENGTH)); } } } } return DigestUtils.sha(dimensionIdStringToHash.toString()); } /** * Rollbacks passed {@code tx} transaction if such is not null. * Assumption is being made that {@code tx} being null means transaction * has been successfully comitted. * * @param tx {@link Transaction} object */ private void rollbackIfNotNull(final Transaction tx) { if (tx != null) { try { tx.rollback(); } catch (RuntimeException rbe) { LOGGER.error("Couldnt roll back transaction", rbe); } } } private static class LookupHelper { static final LookupHelper NOOP_HELPER = new LookupHelper(); public Criteria apply(@Nonnull final Criteria input) { return input; // by default we do nothing } public Query apply(@Nonnull final Query query) { return query; } public String formatHQL(@Nonnull final String hqlQuery) { return String.format(hqlQuery, "1=1"); // by default no formatting } } }