package org.ff4j.audit.repository;
import static org.ff4j.audit.EventConstants.ACTION_CHECK_OK;
import static org.ff4j.store.JdbcStoreConstants.*;
/*
* #%L ff4j-core %% Copyright (C) 2013 - 2015 Ff4J %% 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. #L%
*/
import static org.ff4j.utils.JdbcUtils.closeConnection;
import static org.ff4j.utils.JdbcUtils.closeResultSet;
import static org.ff4j.utils.JdbcUtils.closeStatement;
import static org.ff4j.utils.JdbcUtils.executeUpdate;
import static org.ff4j.utils.JdbcUtils.isTableExist;
import static org.ff4j.utils.JdbcUtils.rollback;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
import org.ff4j.audit.Event;
import org.ff4j.audit.EventQueryDefinition;
import org.ff4j.audit.EventSeries;
import org.ff4j.audit.MutableHitCount;
import org.ff4j.audit.chart.TimeSeriesChart;
import org.ff4j.exception.AuditAccessException;
import org.ff4j.exception.FeatureAccessException;
import org.ff4j.store.JdbcEventMapper;
import org.ff4j.store.JdbcQueryBuilder;
import org.ff4j.utils.MappingUtil;
import org.ff4j.utils.Util;
/**
* Implementation of in memory {@link EventRepository} with limited events.
*
* @author Cedrick Lunven (@clunven)
*/
public class JdbcEventRepository extends AbstractEventRepository {
/** Error message 1. */
public static final String CANNOT_READ_AUDITTABLE = "Cannot read audit table from DB";
/** error message. */
public static final String CANNOT_BUILD_PIE_CHART_FROM_REPOSITORY = "Cannot build PieChart from repository, ";
/** Access to storage. */
private DataSource dataSource;
/** Query builder. */
private JdbcQueryBuilder queryBuilder;
/** Mapper to read from SQL result. */
private static final JdbcEventMapper EVENT_MAPPER = new JdbcEventMapper();
/**
* Constructor from DataSource.
*
* @param jdbcDS
* native jdbc datasource
*/
public JdbcEventRepository(DataSource jdbcDS) {
this.dataSource = jdbcDS;
}
/** {@inheritDoc} */
@Override
public void createSchema() {
DataSource ds = getDataSource();
JdbcQueryBuilder qb = getQueryBuilder();
if (!isTableExist(ds, qb.getTableNameAudit())) {
executeUpdate(ds, qb.sqlCreateTableAudit());
}
}
/** {@inheritDoc} */
// FIXME Stop dynamic query !
@Override
public boolean saveEvent(Event evt) {
Util.assertEvent(evt);
Connection sqlConn = null;
PreparedStatement stmt = null;
try {
// Get collection from Pool
sqlConn = dataSource.getConnection();
// Open TX Bloc
sqlConn.setAutoCommit(false);
int idx = 9;
Map < Integer, String > statementParams = new HashMap<Integer, String>();
StringBuilder sb = new StringBuilder("INSERT INTO " + getQueryBuilder().getTableNameAudit() +
"(EVT_UUID,EVT_TIME,EVT_TYPE,EVT_NAME,EVT_ACTION,EVT_HOSTNAME,EVT_SOURCE,EVT_DURATION");
if (Util.hasLength(evt.getUser())) {
sb.append(", EVT_USER");
statementParams.put(idx, evt.getUser());
idx++;
}
if (Util.hasLength(evt.getValue())) {
sb.append(", EVT_VALUE");
statementParams.put(idx, evt.getValue());
idx++;
}
if (!evt.getCustomKeys().isEmpty()) {
sb.append(", EVT_KEYS");
statementParams.put(idx, MappingUtil.fromMap(evt.getCustomKeys()));
idx++;
}
sb.append(") VALUES (?");
for(int offset = 1; offset < idx-1;offset++) {
sb.append(",?");
}
sb.append(")");
stmt = sqlConn.prepareStatement(sb.toString());
stmt.setString(1, evt.getUuid());
stmt.setTimestamp(2, new java.sql.Timestamp(evt.getTimestamp()));
stmt.setString(3, evt.getType());
stmt.setString(4, evt.getName());
stmt.setString(5, evt.getAction());
stmt.setString(6, evt.getHostName());
stmt.setString(7, evt.getSource());
stmt.setLong(8, evt.getDuration());
for (int id = 9;id < idx;id++) {
stmt.setString(id, statementParams.get(id));
}
// Execute Query
stmt.executeUpdate();
// Commit TX
sqlConn.commit();
} catch(Exception exc) {
rollback(sqlConn);
throw new AuditAccessException("Cannot insert event into DB (" + exc.getClass() + ") "+ exc.getCause(), exc);
} finally {
closeStatement(stmt);
closeConnection(sqlConn);
}
return true;
}
/** {@inheritDoc} */
@Override
public Event getEventByUUID(String uuid, Long timestamp) {
Util.assertHasLength(uuid);
Connection sqlConn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
sqlConn = getDataSource().getConnection();
ps = sqlConn.prepareStatement(getQueryBuilder().getEventByUuidQuery());
ps.setString(1, uuid);
rs = ps.executeQuery();
if (rs.next()) {
return EVENT_MAPPER.mapEvent(rs);
}
return null;
} catch (SQLException sqlEX) {
throw new IllegalStateException("CANNOT_READ_AUDITTABLE", sqlEX);
} finally {
closeResultSet(rs);
closeStatement(ps);
closeConnection(sqlConn);
}
}
/** {@inheritDoc} */
@Override
public void purgeAuditTrail(EventQueryDefinition qDef) {
Util.assertNotNull(qDef);
Connection sqlConn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
sqlConn = getDataSource().getConnection();
ps = sqlConn.prepareStatement(getQueryBuilder().getPurgeAuditTrailQuery(qDef));
ps.setTimestamp(1, new java.sql.Timestamp(qDef.getFrom()));
ps.setTimestamp(2, new java.sql.Timestamp(qDef.getTo()));
ps.executeUpdate();
} catch (SQLException sqlEX) {
throw new IllegalStateException("CANNOT_READ_AUDITTABLE", sqlEX);
} finally {
closeResultSet(rs);
closeStatement(ps);
closeConnection(sqlConn);
}
}
/** {@inheritDoc} */
@Override
public void purgeFeatureUsage(EventQueryDefinition qDef) {
Util.assertNotNull(qDef);
// Enforce remove "checks"
qDef.getActionFilters().add(ACTION_CHECK_OK);
Connection sqlConn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
sqlConn = getDataSource().getConnection();
ps = sqlConn.prepareStatement(getQueryBuilder().getPurgeFeatureUsageQuery(qDef));
ps.setTimestamp(1, new java.sql.Timestamp(qDef.getFrom()));
ps.setTimestamp(2, new java.sql.Timestamp(qDef.getTo()));
ps.executeUpdate();
} catch (SQLException sqlEX) {
throw new IllegalStateException("CANNOT_READ_AUDITTABLE", sqlEX);
} finally {
closeResultSet(rs);
closeStatement(ps);
closeConnection(sqlConn);
}
}
/** {@inheritDoc} */
private EventSeries searchEvents(String sqlQuery, long from, long to) {
Connection sqlConn = null;
PreparedStatement ps = null;
ResultSet rs = null;
EventSeries es = new EventSeries();
try {
sqlConn = getDataSource().getConnection();
ps = sqlConn.prepareStatement(sqlQuery);
ps.setTimestamp(1, new Timestamp(from));
ps.setTimestamp(2, new Timestamp(to));
rs = ps.executeQuery();
while (rs.next()) {
es.add(EVENT_MAPPER.mapEvent(rs));
}
} catch (SQLException sqlEX) {
throw new IllegalStateException("CANNOT_READ_AUDITTABLE", sqlEX);
} finally {
closeResultSet(rs);
closeStatement(ps);
closeConnection(sqlConn);
}
return es;
}
/** {@inheritDoc} */
private Map<String, MutableHitCount> computeHitCount(String sqlQuery, String columnName, long from, long to) {
Connection sqlConn = null;
PreparedStatement ps = null;
ResultSet rs = null;
Map<String, MutableHitCount> hitCount = new HashMap<String, MutableHitCount>();
try {
// Returns features
sqlConn = dataSource.getConnection();
ps = sqlConn.prepareStatement(sqlQuery);
ps.setTimestamp(1, new Timestamp(from));
ps.setTimestamp(2, new Timestamp(to));
rs = ps.executeQuery();
while (rs.next()) {
hitCount.put(rs.getString(columnName), new MutableHitCount(rs.getInt("NB")));
}
} catch (SQLException sqlEX) {
throw new FeatureAccessException(CANNOT_BUILD_PIE_CHART_FROM_REPOSITORY, sqlEX);
} finally {
closeResultSet(rs);
closeStatement(ps);
closeConnection(sqlConn);
}
return hitCount;
}
/** {@inheritDoc} */
@Override
public EventSeries getAuditTrail(EventQueryDefinition qDef) {
return searchEvents(getQueryBuilder().getSelectAuditTrailQuery(qDef), qDef.getFrom(), qDef.getTo());
}
/** {@inheritDoc} */
@Override
public EventSeries searchFeatureUsageEvents(EventQueryDefinition qDef) {
return searchEvents(getQueryBuilder().getSelectFeatureUsageQuery(qDef), qDef.getFrom(), qDef.getTo());
}
/** {@inheritDoc} */
@Override
public Map<String, MutableHitCount> getFeatureUsageHitCount(EventQueryDefinition query) {
return computeHitCount(getQueryBuilder().getFeaturesHitCount(), COL_EVENT_NAME, query.getFrom(), query.getTo());
}
/** {@inheritDoc} */
@Override
public Map<String, MutableHitCount> getHostHitCount(EventQueryDefinition query) {
return computeHitCount(getQueryBuilder().getHostHitCount(), COL_EVENT_HOSTNAME, query.getFrom(), query.getTo());
}
/** {@inheritDoc} */
@Override
public Map<String, MutableHitCount> getUserHitCount(EventQueryDefinition query) {
return computeHitCount(getQueryBuilder().getUserHitCount(), COL_EVENT_USER, query.getFrom(), query.getTo());
}
/** {@inheritDoc} */
@Override
public Map<String, MutableHitCount> getSourceHitCount(EventQueryDefinition query) {
return computeHitCount(getQueryBuilder().getSourceHitCount(), COL_EVENT_SOURCE, query.getFrom(), query.getTo());
}
/** {@inheritDoc} */
@Override
public TimeSeriesChart getFeatureUsageHistory(EventQueryDefinition query, TimeUnit units) {
// Create the interval depending on units
TimeSeriesChart tsc = new TimeSeriesChart(query.getFrom(), query.getTo(), units);
// Search All events
Iterator<Event> iterEvent = searchFeatureUsageEvents(query).iterator();
// Dispatch events into time slots
while (iterEvent.hasNext()) {
tsc.addEvent(iterEvent.next());
}
return tsc;
}
/**
* Getter accessor for attribute 'dataSource'.
*
* @return current value of 'dataSource'
*/
public DataSource getDataSource() {
return dataSource;
}
/**
* Setter accessor for attribute 'dataSource'.
*
* @param dataSource
* new value for 'dataSource '
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* @return the queryBuilder
*/
public JdbcQueryBuilder getQueryBuilder() {
if (queryBuilder == null) {
queryBuilder = new JdbcQueryBuilder();
}
return queryBuilder;
}
/**
* @param queryBuilder the queryBuilder to set
*/
public void setQueryBuilder(JdbcQueryBuilder queryBuilder) {
this.queryBuilder = queryBuilder;
}
}