/*
* Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package org.entando.entando.aps.system.services.actionlog;
import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.entando.entando.aps.system.services.actionlog.model.ActionLogRecord;
import org.entando.entando.aps.system.services.actionlog.model.ActivityStreamInfo;
import org.entando.entando.aps.system.services.actionlog.model.IActionLogRecordSearchBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.agiletec.aps.system.common.AbstractSearcherDAO;
import com.agiletec.aps.system.common.FieldSearchFilter;
import com.agiletec.aps.system.services.group.Group;
import org.entando.entando.aps.system.services.actionlog.model.IActivityStreamSearchBean;
/**
* @author E.Santoboni
*/
public class ActionLogDAO extends AbstractSearcherDAO implements IActionLogDAO {
private static final Logger _logger = LoggerFactory.getLogger(ActionLogDAO.class);
@Override
public void addActionRecord(ActionLogRecord actionRecord) {
Connection conn = null;
PreparedStatement stat = null;
try {
conn = this.getConnection();
conn.setAutoCommit(false);
stat = conn.prepareStatement(ADD_ACTION_RECORD);
stat.setInt(1, actionRecord.getId());
stat.setString(2, actionRecord.getUsername());
Timestamp timestamp = new Timestamp(actionRecord.getActionDate().getTime());
stat.setTimestamp(3, timestamp);
stat.setString(4, actionRecord.getNamespace());
stat.setString(5, actionRecord.getActionName());
stat.setString(6, actionRecord.getParameters());
ActivityStreamInfo asi = actionRecord.getActivityStreamInfo();
if (null != asi) {
stat.setString(7, ActivityStreamInfoDOM.marshalInfo(asi));
} else {
stat.setNull(7, Types.VARCHAR);
}
stat.setTimestamp(8, timestamp);
stat.executeUpdate();
this.addLogRecordRelations(actionRecord.getId(), asi, conn);
conn.commit();
} catch (Throwable t) {
this.executeRollback(conn);
_logger.error("Error on insert actionlogger record", t);
throw new RuntimeException("Error on insert actionlogger record", t);
} finally {
closeDaoResources(null, stat, conn);
}
}
private void addLogRecordRelations(int recordId, ActivityStreamInfo asi, Connection conn) {
if (asi == null) {
return;
}
List<String> groups = asi.getGroups();
if (null == groups || groups.isEmpty()) {
return;
}
Set<String> codes = new HashSet<String>(groups);
Iterator<String> iterator = codes.iterator();
PreparedStatement stat = null;
try {
stat = conn.prepareStatement(ADD_LOG_RECORD_RELATION);
while (iterator.hasNext()) {
String groupCode = iterator.next();
stat.setInt(1, recordId);
stat.setString(2, groupCode);
stat.addBatch();
stat.clearParameters();
}
stat.executeBatch();
} catch (BatchUpdateException e) {
_logger.error("Error adding relation for record {}", recordId, e);
throw new RuntimeException("Error adding relation for record - " + recordId, e.getNextException());
} catch (Throwable t) {
_logger.error("Error adding relations for record {}", recordId, t);
throw new RuntimeException("Error adding relations for record - " + recordId, t);
} finally {
closeDaoResources(null, stat);
}
}
@Override
public List<Integer> getActionRecords(IActionLogRecordSearchBean searchBean) {
FieldSearchFilter[] filters = this.createFilters(searchBean);
if(searchBean != null && searchBean.getUserGroupCodes() != null && !searchBean.getUserGroupCodes().isEmpty()){
return this.getActionRecords(filters, searchBean.getUserGroupCodes());
}
return this.getActionRecords(filters);
}
@Override
public List<Integer> getActivityStreamRecords(IActivityStreamSearchBean searchBean) {
return getActionRecords(searchBean);
}
@Override
public List<Integer> getActionRecords(FieldSearchFilter[] filters, List<String> userGroupCodes) {
Connection conn = null;
List<Integer> idList = new ArrayList<Integer>();
PreparedStatement stat = null;
ResultSet result = null;
try {
conn = this.getConnection();
List<String> groupCodes = (null != userGroupCodes && userGroupCodes.contains(Group.ADMINS_GROUP_NAME)) ? null : userGroupCodes;
stat = this.buildStatement(filters, groupCodes, conn);
result = stat.executeQuery();
while (result.next()) {
idList.add(result.getInt(1));
}
} catch (Throwable t) {
_logger.error("Error loading actionlogger records", t);
throw new RuntimeException("Error loading actionlogger records", t);
} finally {
closeDaoResources(result, stat, conn);
}
return idList;
}
@Override
public List<Integer> getActionRecords(FieldSearchFilter[] filters) {
List<Integer> actionRecords = new ArrayList<Integer>();
try {
List<String> ids = super.searchId(filters);
if (null != ids) {
for (int i = 0; i < ids.size(); i++) {
String id = ids.get(i);
actionRecords.add(Integer.parseInt(id));
}
}
} catch (Throwable t) {
_logger.error("Error loading actionlogger records", t);
throw new RuntimeException("Error loading actionlogger records", t);
}
return actionRecords;
}
@Override
public List<Integer> getActivityStream(List<String> userGroupCodes) {
Connection conn = null;
List<Integer> idList = new ArrayList<Integer>();
PreparedStatement stat = null;
ResultSet result = null;
try {
conn = this.getConnection();
FieldSearchFilter filter1 = new FieldSearchFilter("actiondate");
filter1.setOrder(FieldSearchFilter.DESC_ORDER);
FieldSearchFilter filter2 = new FieldSearchFilter("activitystreaminfo");
FieldSearchFilter[] filters = {filter1, filter2};
List<String> groupCodes = (null != userGroupCodes && userGroupCodes.contains(Group.ADMINS_GROUP_NAME)) ? null : userGroupCodes;
stat = this.buildStatement(filters, groupCodes, conn);
result = stat.executeQuery();
while (result.next()) {
idList.add(result.getInt(1));
}
} catch (Throwable t) {
_logger.error("Error loading activity stream records", t);
throw new RuntimeException("Error loading activity stream records", t);
} finally {
closeDaoResources(result, stat, conn);
}
return idList;
}
private PreparedStatement buildStatement(FieldSearchFilter[] filters, Collection<String> groupCodes, Connection conn) {
String query = this.createQueryString(filters, groupCodes);
PreparedStatement stat = null;
try {
stat = conn.prepareStatement(query);
int index = 0;
index = this.addMetadataFieldFilterStatementBlock(filters, index, stat);
index = this.addGroupStatementBlock(groupCodes, index, stat);
} catch (Throwable t) {
_logger.error("Error creating the statement", t);
throw new RuntimeException("Error creating the statement", t);
}
return stat;
}
protected String createQueryString(FieldSearchFilter[] filters, Collection<String> groupCodes) {
StringBuffer query = this.createBaseQueryBlock(filters, false);
this.appendJoinTableRefQueryBlock(query, groupCodes);
boolean hasAppendWhereClause = this.appendMetadataFieldFilterQueryBlocks(filters, query, false);
if (null != groupCodes && !groupCodes.isEmpty()) {
hasAppendWhereClause = this.verifyWhereClauseAppend(query, hasAppendWhereClause);
query.append(" ( ");
int size = groupCodes.size();
for (int i = 0; i < size; i++) {
if (i != 0) {
query.append("OR ");
}
query.append("actionlogrelations.refgroup = ? ");
}
query.append(") ");
}
boolean ordered = appendOrderQueryBlocks(filters, query, false);
return query.toString();
}
private void appendJoinTableRefQueryBlock(StringBuffer query, Collection<String> groupCodes) {
if (null == groupCodes || groupCodes.isEmpty()) {
return;
}
String masterTableName = this.getMasterTableName();
String masterTableIdFieldName = this.getMasterTableIdFieldName();
query.append("INNER JOIN ");
query.append("actionlogrelations").append(" ON ")
.append(masterTableName).append(".").append(masterTableIdFieldName).append(" = ")
.append("actionlogrelations").append(".").append("recordid").append(" ");
}
protected int addGroupStatementBlock(Collection<String> groupCodes, int index, PreparedStatement stat) throws Throwable {
if (null != groupCodes && !groupCodes.isEmpty()) {
Iterator<String> groupIter = groupCodes.iterator();
while (groupIter.hasNext()) {
String groupName = groupIter.next();
stat.setString(++index, groupName);
}
}
return index;
}
protected FieldSearchFilter[] createFilters(IActionLogRecordSearchBean searchBean) {
FieldSearchFilter[] filters = new FieldSearchFilter[0];
if (null != searchBean) {
String username = searchBean.getUsername();
if (null != username && username.trim().length() > 0) {
FieldSearchFilter filter = new FieldSearchFilter("username", this.extractSearchValues(username), true);
filters = super.addFilter(filters, filter);
}
String namespace = searchBean.getNamespace();
if (null != namespace && namespace.trim().length() > 0) {
FieldSearchFilter filter = new FieldSearchFilter("namespace", this.extractSearchValues(namespace), true);
filters = super.addFilter(filters, filter);
}
String actionName = searchBean.getActionName();
if (null != actionName && actionName.trim().length() > 0) {
FieldSearchFilter filter = new FieldSearchFilter("actionname", this.extractSearchValues(actionName), true);
filters = super.addFilter(filters, filter);
}
String parameters = searchBean.getParams();
if (null != parameters && parameters.trim().length() > 0) {
FieldSearchFilter filter = new FieldSearchFilter("parameters", this.extractSearchValues(parameters), true);
filters = super.addFilter(filters, filter);
}
Date startCreation = searchBean.getStartCreation();
Date endCreation = searchBean.getEndCreation();
if (null != startCreation || null != endCreation) {
Timestamp tsStart = (null != startCreation) ? new Timestamp(startCreation.getTime()) : null;
Timestamp tsEnd = (null != endCreation) ? new Timestamp(endCreation.getTime()) : null;
FieldSearchFilter filter = new FieldSearchFilter("actiondate", tsStart, tsEnd);
filter.setOrder(FieldSearchFilter.Order.DESC);
filters = super.addFilter(filters, filter);
}
Date startUpdate = searchBean.getStartUpdate();
Date endUpdate = searchBean.getEndUpdate();
if (null != startUpdate || null != endUpdate) {
Timestamp tsStart = (null != startUpdate) ? new Timestamp(startUpdate.getTime()) : null;
Timestamp tsEnd = (null != endUpdate) ? new Timestamp(endUpdate.getTime()) : null;
FieldSearchFilter filter = new FieldSearchFilter("updatedate", tsStart, tsEnd);
filter.setOrder(FieldSearchFilter.Order.DESC);
filters = super.addFilter(filters, filter);
}
if (searchBean instanceof IActivityStreamSearchBean) {
FieldSearchFilter filter = new FieldSearchFilter("activitystreaminfo");
filters = super.addFilter(filters, filter);
}
}
return filters;
}
protected List<String> extractSearchValues(String text) {
String[] titleSplit = text.trim().split(" ");
return (List<String>) Arrays.asList(titleSplit);
}
@Override
public ActionLogRecord getActionRecord(int id) {
Connection conn = null;
PreparedStatement stat = null;
ResultSet res = null;
ActionLogRecord actionRecord = null;
try {
conn = this.getConnection();
conn.setAutoCommit(false);
stat = conn.prepareStatement(GET_ACTION_RECORD);
stat.setInt(1, id);
res = stat.executeQuery();
if (res.next()) {
actionRecord = new ActionLogRecord();
actionRecord.setId(id);
Timestamp actionDate = res.getTimestamp("actiondate");
actionRecord.setActionDate(new Date(actionDate.getTime()));
Timestamp updateDate = res.getTimestamp("updatedate");
actionRecord.setUpdateDate(new Date(updateDate.getTime()));
actionRecord.setActionName(res.getString("actionname"));
actionRecord.setNamespace(res.getString("namespace"));
actionRecord.setParameters(res.getString("parameters"));
actionRecord.setUsername(res.getString("username"));
String asiXml = res.getString("activitystreaminfo");
if (null != asiXml && asiXml.trim().length() > 0) {
ActivityStreamInfo asi = ActivityStreamInfoDOM.unmarshalInfo(asiXml);
actionRecord.setActivityStreamInfo(asi);
}
}
conn.commit();
} catch (Throwable t) {
this.executeRollback(conn);
_logger.error("Error loading actionlogger record with id: {}", id, t);
throw new RuntimeException("Error loading actionlogger record with id: " + id, t);
} finally {
closeDaoResources(res, stat, conn);
}
return actionRecord;
}
@Override
public void deleteActionRecord(int id) {
Connection conn = null;
try {
conn = this.getConnection();
conn.setAutoCommit(false);
super.executeQueryWithoutResultset(conn, DELETE_LOG_RECORD_RELATIONS, id);
super.executeQueryWithoutResultset(conn, DELETE_LOG_RECORD, id);
conn.commit();
} catch (Throwable t) {
this.executeRollback(conn);
_logger.error("Error on delete record: {}", id, t);
throw new RuntimeException("Error on delete record: " + id , t);
} finally {
closeConnection(conn);
}
}
@Override
public void updateRecordDate(int id) {
Connection conn = null;
try {
conn = this.getConnection();
conn.setAutoCommit(false);
Timestamp timestamp = new Timestamp(new Date().getTime());
super.executeQueryWithoutResultset(conn, UPDATE_UPDATEDATE_ACTION_RECORD, timestamp, id);
conn.commit();
} catch (Throwable t) {
this.executeRollback(conn);
_logger.error("Error updating record date: {}", id, t);
throw new RuntimeException("Error updating record date: id " + id , t);
} finally {
this.closeConnection(conn);
}
}
@Override
protected String getMasterTableName() {
return "actionlogrecords";
}
@Override
protected String getMasterTableIdFieldName() {
return "id";
}
@Override
protected String getTableFieldName(String metadataFieldKey) {
return metadataFieldKey;
}
@Override
protected boolean isForceCaseInsensitiveLikeSearch() {
return true;
}
@Override
public Set<Integer> extractOldRecords(Integer maxActivitySizeByGroup) {
Set<Integer> recordsToDelete = new HashSet<Integer>();
Connection conn = null;
try {
conn = this.getConnection();
Map<String, Integer> occurrences = this.getOccurrences(maxActivitySizeByGroup, conn);
Iterator<String> groupIter = occurrences.keySet().iterator();
while (groupIter.hasNext()) {
String groupName = groupIter.next();
this.extractRecordToDelete(groupName, maxActivitySizeByGroup, recordsToDelete, conn);
}
} catch (Throwable t) {
_logger.error("Error extracting old Stream logs", t);
throw new RuntimeException("Error cleaning old Stream logs", t);
} finally {
this.closeConnection(conn);
}
return recordsToDelete;
}
private Map<String, Integer> getOccurrences(Integer maxActivitySizeByGroup, Connection conn) {
Map<String, Integer> occurrences = new HashMap<String, Integer>();
Statement stat = null;
ResultSet res = null;
try {
stat = conn.createStatement();
res = stat.executeQuery(GET_GROUP_OCCURRENCES);
while (res.next()) {
String groupName = res.getString(1);
Integer integer = res.getInt(2);
if (null != maxActivitySizeByGroup && integer > maxActivitySizeByGroup) {
occurrences.put(groupName, integer);
}
}
} catch (Throwable t) {
_logger.error("Error loading actionlogger occurrences", t);
throw new RuntimeException("Error loading actionlogger occurrences", t);
} finally {
closeDaoResources(res, stat);
}
return occurrences;
}
private void extractRecordToDelete(String groupName,
Integer maxActivitySizeByGroup, Set<Integer> recordIds, Connection conn) {
PreparedStatement stat = null;
ResultSet result = null;
try {
List<Integer> idList = new ArrayList<Integer>();
FieldSearchFilter filter1 = new FieldSearchFilter("actiondate");
filter1.setOrder(FieldSearchFilter.Order.DESC);
FieldSearchFilter filter2 = new FieldSearchFilter("activitystreaminfo");
FieldSearchFilter[] filters = {filter1, filter2};
List<String> groupCodes = new ArrayList<String>();
groupCodes.add(groupName);
stat = this.buildStatement(filters, groupCodes, conn);
result = stat.executeQuery();
while (result.next()) {
Integer id = result.getInt(1);
if (!idList.contains(id)) {
idList.add(id);
}
}
if (idList.size() > maxActivitySizeByGroup) {
for (int i = maxActivitySizeByGroup; i < idList.size(); i++) {
Integer id = idList.get(i);
recordIds.add(id);
}
}
} catch (Throwable t) {
_logger.error("Error while loading activity stream records to delete : group {}", groupName, t);
throw new RuntimeException("Error while loading activity stream records to delete : group '" + groupName + "'", t);
} finally {
closeDaoResources(result, stat);
}
}
private static final String ADD_ACTION_RECORD
= "INSERT INTO actionlogrecords ( id, username, actiondate, namespace, actionname, parameters, activitystreaminfo, updatedate) "
+ "VALUES ( ? , ? , ? , ? , ? , ? , ? , ? )";
private static final String GET_ACTION_RECORD
= "SELECT username, actiondate, updatedate, namespace, actionname, parameters, activitystreaminfo FROM actionlogrecords WHERE id = ?";
private static final String DELETE_LOG_RECORD
= "DELETE from actionlogrecords where id = ?";
private static final String DELETE_LOG_RECORD_RELATIONS
= "DELETE from actionlogrelations where recordid = ?";
private final String ADD_LOG_RECORD_RELATION
= "INSERT INTO actionlogrelations (recordid, refgroup) VALUES ( ? , ? )";
private static final String GET_GROUP_OCCURRENCES
= "SELECT refgroup, count(refgroup) FROM actionlogrelations GROUP BY refgroup";
private static final String UPDATE_UPDATEDATE_ACTION_RECORD
= "UPDATE actionlogrecords SET updatedate = ? WHERE id = ?";
}