package rocks.inspectit.server.dao.impl;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import rocks.inspectit.server.dao.StorageDataDao;
import rocks.inspectit.server.util.JpaUtil;
import rocks.inspectit.shared.all.communication.DefaultData;
import rocks.inspectit.shared.all.communication.SystemSensorData;
import rocks.inspectit.shared.all.communication.data.SystemInformationData;
import rocks.inspectit.shared.all.exception.BusinessException;
import rocks.inspectit.shared.all.exception.enumeration.StorageErrorCodeEnum;
import rocks.inspectit.shared.all.indexing.IIndexQuery;
import rocks.inspectit.shared.cs.indexing.buffer.IBufferTreeComponent;
import rocks.inspectit.shared.cs.indexing.impl.IndexingException;
import rocks.inspectit.shared.cs.indexing.query.provider.impl.IndexQueryProvider;
import rocks.inspectit.shared.cs.indexing.restriction.impl.IndexQueryRestrictionFactory;
import rocks.inspectit.shared.cs.storage.label.AbstractStorageLabel;
import rocks.inspectit.shared.cs.storage.label.StringStorageLabel;
import rocks.inspectit.shared.cs.storage.label.type.AbstractStorageLabelType;
import rocks.inspectit.shared.cs.storage.label.type.impl.AssigneeLabelType;
import rocks.inspectit.shared.cs.storage.label.type.impl.RatingLabelType;
import rocks.inspectit.shared.cs.storage.label.type.impl.StatusLabelType;
import rocks.inspectit.shared.cs.storage.label.type.impl.UseCaseLabelType;
/**
* DAO support for the storage purposes.
*
* @author Ivan Senic
*
*/
@Repository
public class StorageDataDaoImpl implements StorageDataDao {
/**
* {@link IndexQueryProvider}.
*/
@Autowired
private IndexQueryProvider indexQueryProvider;
/**
* {@link IndexingException} tree.
*/
@Autowired
private IBufferTreeComponent<DefaultData> indexingTree;
/**
* Entity manager.
*/
@PersistenceContext
private EntityManager entityManager;
/**
* Transaction template to use to do init work.
*/
private TransactionTemplate tt;
/**
* Default constructor.
* <p>
* Needs {@link PlatformTransactionManager} for instantiating the {@link TransactionTemplate} to
* execute the initialization.
*
* @param transactionManager
* {@link PlatformTransactionManager}. Autowired by Spring.
*/
@Autowired
public StorageDataDaoImpl(PlatformTransactionManager transactionManager) {
this.tt = new TransactionTemplate(transactionManager);
}
/**
* {@inheritDoc}
*/
@Override
public boolean saveLabel(AbstractStorageLabel<?> label) {
if (label.getStorageLabelType().isValueReusable()) {
List<?> exampleFind = loadAll(label.getClass());
if (!exampleFind.contains(label)) {
AbstractStorageLabelType<?> labelType = label.getStorageLabelType();
if (null == labelType) {
return false;
}
if ((labelType.getId() == 0) && !labelType.isMultiType()) {
return false;
}
entityManager.persist(label);
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void removeLabel(AbstractStorageLabel<?> label) {
if (label.getStorageLabelType().isValueReusable()) {
JpaUtil.delete(entityManager, label);
}
}
/**
* {@inheritDoc}
*/
@Override
public void removeLabels(Collection<AbstractStorageLabel<?>> labels) {
for (AbstractStorageLabel<?> label : labels) {
this.removeLabel(label);
}
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public List<AbstractStorageLabel<?>> getAllLabels() {
Query query = entityManager.createNamedQuery(AbstractStorageLabel.FIND_ALL);
return query.getResultList();
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public <E> List<AbstractStorageLabel<E>> getAllLabelsForType(AbstractStorageLabelType<E> labelType) {
Query query = entityManager.createNamedQuery(AbstractStorageLabel.FIND_BY_LABEL_TYPE);
query.setParameter("storageLabelType", labelType);
return query.getResultList();
}
/**
* {@inheritDoc}
*/
@Override
public void saveLabelType(AbstractStorageLabelType<?> labelType) {
if (labelType.isMultiType()) {
entityManager.persist(labelType);
} else {
List<?> findByClass = loadAll(labelType.getClass());
if (findByClass.isEmpty()) {
entityManager.persist(labelType);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void removeLabelType(AbstractStorageLabelType<?> labelType) throws BusinessException {
if (getAllLabelsForType(labelType).isEmpty()) {
JpaUtil.delete(entityManager, labelType);
} else {
throw new BusinessException("Delete label type " + labelType.getClass().getSimpleName() + ".", StorageErrorCodeEnum.LABEL_TYPE_CAN_NOT_BE_DELETED);
}
}
/**
* {@inheritDoc}
*/
@Override
public <E extends AbstractStorageLabelType<?>> List<E> getLabelTypes(Class<E> labelTypeClass) {
return loadAll(labelTypeClass);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public List<AbstractStorageLabelType<?>> getAllLabelTypes() {
Query query = entityManager.createNamedQuery(AbstractStorageLabelType.FIND_ALL);
return query.getResultList();
}
/**
* {@inheritDoc}
*/
@Override
public List<DefaultData> getAllDefaultDataForAgent(long platformId, Date fromDate, Date toDate) {
List<DefaultData> results = new ArrayList<>();
// first load all from buffer
IIndexQuery query = indexQueryProvider.createNewIndexQuery();
query.setPlatformIdent(platformId);
if (null != fromDate) {
query.setFromDate(new Timestamp(fromDate.getTime()));
}
if (null != toDate) {
query.setToDate(new Timestamp(toDate.getTime()));
}
List<DefaultData> bufferData = indexingTree.query(query);
if (CollectionUtils.isNotEmpty(bufferData)) {
results.addAll(bufferData);
}
// then load all System sensor data from DB
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<SystemSensorData> criteria = builder.createQuery(SystemSensorData.class);
Root<? extends SystemSensorData> root = criteria.from(SystemSensorData.class);
Predicate platformIdPredicate = builder.equal(root.get("platformIdent"), platformId);
Predicate timestampPredicate = null;
if ((null != fromDate) && (null != toDate)) {
timestampPredicate = builder.between(root.<Timestamp> get("timeStamp"), new Timestamp(fromDate.getTime()), new Timestamp(toDate.getTime()));
}
if (null != timestampPredicate) {
criteria.where(platformIdPredicate, timestampPredicate);
} else {
criteria.where(platformIdPredicate);
}
List<SystemSensorData> sensorDatas = entityManager.createQuery(criteria).getResultList();
// combine results
if (CollectionUtils.isNotEmpty(sensorDatas)) {
results.addAll(sensorDatas);
}
// since we only have one system information data per agent connection
// we need to manually add it as we can not be sure that the time stamp of the oldest
// element in the buffer will include the system data send on the agent connection
List<SystemInformationData> systemInformationData = getSystemInformationData(Collections.singletonList(platformId));
if (CollectionUtils.isNotEmpty(systemInformationData)) {
results.addAll(systemInformationData);
}
return results;
}
/**
* {@inheritDoc}
*/
@Override
public List<DefaultData> getDataFromIdList(Collection<Long> elementIds, long platformIdent) {
IIndexQuery query = indexQueryProvider.createNewIndexQuery();
query.addIndexingRestriction(IndexQueryRestrictionFactory.isInCollection("id", elementIds));
query.setPlatformIdent(platformIdent);
return indexingTree.query(query);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public List<SystemInformationData> getSystemInformationData(Collection<Long> agentIds) {
Query query = entityManager.createNamedQuery(SystemInformationData.FIND_LATEST_FOR_PLATFORM_IDS);
query.setParameter("platformIdents", agentIds);
return query.getResultList();
}
/**
* Initializes the default label list.
* <p>
* Due to to the fact that when PostConstruct method is fired Spring context might not be yet
* initialized, there is no guarantee that {@link #createDefaultLabelList()} will be executed in
* transactional context even if we annotate it with Transactional. Thus we need to execute
* creation with programmatic transaction management.
*/
@PostConstruct
public void init() {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
createDefaultLabelList();
}
});
}
/**
* Create set of default labels.
*/
private void createDefaultLabelList() {
this.saveLabelType(new AssigneeLabelType());
this.saveLabelType(new UseCaseLabelType());
List<RatingLabelType> ratingLabelTypeList = this.getLabelTypes(RatingLabelType.class);
RatingLabelType ratingLabelType;
if (CollectionUtils.isNotEmpty(ratingLabelTypeList)) {
ratingLabelType = ratingLabelTypeList.get(0);
} else {
ratingLabelType = new RatingLabelType();
this.saveLabelType(ratingLabelType);
}
List<AbstractStorageLabel<String>> ratingLabelList = this.getAllLabelsForType(ratingLabelType);
if (ratingLabelList.isEmpty()) {
// add default rating labels
this.saveLabel(new StringStorageLabel("Very Bad", ratingLabelType));
this.saveLabel(new StringStorageLabel("Bad", ratingLabelType));
this.saveLabel(new StringStorageLabel("Medium", ratingLabelType));
this.saveLabel(new StringStorageLabel("Good", ratingLabelType));
this.saveLabel(new StringStorageLabel("Very Good", ratingLabelType));
}
List<StatusLabelType> statusLabelTypeList = this.getLabelTypes(StatusLabelType.class);
StatusLabelType statusLabelType;
if (CollectionUtils.isNotEmpty(statusLabelTypeList)) {
statusLabelType = statusLabelTypeList.get(0);
} else {
statusLabelType = new StatusLabelType();
this.saveLabelType(statusLabelType);
}
List<AbstractStorageLabel<String>> statusLabelList = this.getAllLabelsForType(statusLabelType);
if (statusLabelList.isEmpty()) {
// add default status labels
this.saveLabel(new StringStorageLabel("Awaiting Review", statusLabelType));
this.saveLabel(new StringStorageLabel("In-Progress", statusLabelType));
this.saveLabel(new StringStorageLabel("Closed", statusLabelType));
}
}
/**
* Loads all entities of one class.
*
* @param <E>
* Type of entity.
* @param clazz
* Class
* @return List of entities.
*/
private <E> List<E> loadAll(Class<E> clazz) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<E> criteria = builder.createQuery(clazz);
criteria.from(clazz);
return entityManager.createQuery(criteria).getResultList();
}
}