package org.juxtasoftware.dao.impl;
import static eu.interedition.text.rdbms.RelationalTextRepository.selectTextFrom;
import java.io.IOException;
import java.io.Reader;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.lucene.index.CorruptIndexException;
import org.juxtasoftware.dao.ComparisonSetDao;
import org.juxtasoftware.dao.WitnessDao;
import org.juxtasoftware.model.ComparisonSet;
import org.juxtasoftware.model.ComparisonSet.Status;
import org.juxtasoftware.model.ResourceInfo;
import org.juxtasoftware.model.RevisionInfo;
import org.juxtasoftware.model.Usage;
import org.juxtasoftware.model.Witness;
import org.juxtasoftware.model.Workspace;
import org.juxtasoftware.util.LuceneHelper;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
import eu.interedition.text.Range;
import eu.interedition.text.Text;
import eu.interedition.text.TextRepository;
import eu.interedition.text.rdbms.RelationalText;
import eu.interedition.text.rdbms.RelationalTextRepository;
/**
* DAO implementation for juxta witness CRUD
*
* @author loufoster
*
*/
@Repository
public class WitnessDaoImpl implements WitnessDao, InitializingBean {
protected SimpleJdbcInsert insert;
private String tableName;
@Autowired ComparisonSetDao setDao;
@Autowired TextRepository textRepository;
@Autowired @Qualifier("executor") private TaskExecutor taskExecutor;
@Autowired private JdbcTemplate jdbcTemplate;
@Autowired private LuceneHelper lucene;
public WitnessDaoImpl() {
this.tableName = "juxta_witness";
}
@Override
public void afterPropertiesSet() throws Exception {
this.insert = new SimpleJdbcInsert(this.jdbcTemplate).withTableName(tableName).usingGeneratedKeyColumns("id");
}
@Override
public Long create(Witness w) throws CorruptIndexException, IOException {
Long id = insert.executeAndReturnKey(toInsertData(w)).longValue();
// add the new witness to the lucene index
String ws = getWorkspaceName(w.getWorkspaceId());
Long textId = ((RelationalText)w.getText()).getId();
Reader r = getContentStream(w);
this.lucene.addDocument("witness", ws, id, w.getName(), textId, r);
return id;
}
@Override
public ResourceInfo getInfo( final Long witnessId ) {
final String sql =
"select w.id as id, w.name as name,w.created as created,w.updated as updated,ws.name as workspace " +
" from juxta_witness w inner join juxta_workspace ws on workspace_id = ws.id where w.id=?";
return DataAccessUtils.uniqueResult( this.jdbcTemplate.query(sql, new RowMapper<ResourceInfo>(){
@Override
public ResourceInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
return new ResourceInfo(rs.getLong("id"), rs.getString("workspace"), rs.getString("name"), rs.getTimestamp("created"), rs.getTimestamp("updated"));
}}, witnessId));
}
@Override
public void rename(final Witness witness, final String newName) {
final String sql = "update "+this.tableName+" set name = ? where id = ?";
this.jdbcTemplate.update(sql, newName, witness.getId());
}
@Override
public void updateContent( Witness witness, final Text newContent) throws CorruptIndexException, IOException {
Text oldTxt = witness.getText();
Long oldTxtId = ((RelationalText)witness.getText()).getId() ;
Long newTxtId = ((RelationalText)newContent).getId() ;
String sql = "update "+this.tableName+" set text_id=?, updated=? where id=?";
this.jdbcTemplate.update(sql, newTxtId, new Date(), witness.getId() );
this.textRepository.delete( oldTxt );
witness.setText(newContent);
// Update the index: remove the old and add the updted src as new
String ws = getWorkspaceName(witness.getWorkspaceId());
this.lucene.deleteDocument( oldTxtId );
this.lucene.addDocument("witness", ws, witness.getId(), witness.getName(), newTxtId, getContentStream(witness) );
}
private String getWorkspaceName(Long id) {
String sql = "select name from juxta_workspace where id=?";
return this.jdbcTemplate.queryForObject(sql, String.class, id);
}
@Override
public Reader getContentStream(Witness witness) {
final String sql = "select content from text_content where id=?";
return DataAccessUtils.uniqueResult( this.jdbcTemplate.query(sql, new RowMapper<Reader>(){
@Override
public Reader mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs.getCharacterStream("content");
}}, ((RelationalText)witness.getText()).getId() ) );
}
@Override
public List<Witness> list( final Workspace ws) {
StringBuilder sql = getSqlBuilder();
sql.append(" where workspace_id=? order by created desc, updated desc");
return jdbcTemplate.query(sql.toString(), new WitnessMapper(), ws.getId());
}
@Override
public void delete(final Witness witness) {
// kill it from index
this.lucene.deleteDocument( ((RelationalText)witness.getText()).getId() );
// get a list of all uses of this witness.
// Mark sets as NOT collated, clear their collation cache and remove all
// alignments
final List<Usage> usage = getUsage(witness);
final Text delText = witness.getText();
// immediately delete the witness. This is quick, but orphans
// a bunch of data - data that can take a long time to delete.
// Push this extra deletion into a worker thread.
this.jdbcTemplate.update("delete from " + this.tableName + " where id = ?", witness.getId());
final List<ComparisonSet> sets = new ArrayList<ComparisonSet>();
for (Usage u : usage) {
if (u.getType().equals(Usage.Type.COMPARISON_SET)) {
ComparisonSet set = setDao.find(u.getId());
if ( !(set.getStatus().equals(Status.DELETED) || !set.getStatus().equals(Status.NOT_COLLATED)) ) {
set.setStatus(Status.NOT_COLLATED);
this.setDao.update(set);
}
sets.add(set);
}
}
this.taskExecutor.execute(new Runnable() {
@Override
public void run() {
WitnessDaoImpl.this.textRepository.delete(delText);
for (ComparisonSet set : sets) {
setDao.clearCollationData(set);
}
}
});
}
protected SqlParameterSource toInsertData(Witness object) {
final MapSqlParameterSource ps = new MapSqlParameterSource();
ps.addValue("name", object.getName());
ps.addValue("source_id", object.getSourceId());
ps.addValue("xslt_id", object.getXsltId());
ps.addValue("text_id", ((RelationalText) object.getText()).getId());
ps.addValue("workspace_id", object.getWorkspaceId());
ps.addValue("created", new Date());
return ps;
}
@Override
public List<Witness> find(ComparisonSet set) {
StringBuilder sql = getSqlBuilder();
sql.append(" join juxta_comparison_set_member csm on csm.witness_id = w.id");
sql.append(" where csm.set_id = ? order by w_created desc");
return new ArrayList<Witness>(
this.jdbcTemplate.query(sql.toString(), new WitnessMapper(), set.getId()));
}
@Override
public Witness find(Workspace ws, String title) {
StringBuilder sql = getSqlBuilder();
sql.append(" where w.workspace_id = ? and w.name = ?");
return DataAccessUtils.uniqueResult( this.jdbcTemplate.query(sql.toString(),
new WitnessMapper(), ws.getId(), title));
}
@Override
public boolean exists( final Workspace ws, final String title) {
final String sql = "select count(*) as cnt from " +this.tableName+ " where workspace_id=? and name=?";
int cnt = this.jdbcTemplate.queryForInt(sql, ws.getId(), title);
return ( cnt > 0);
}
@Override
public Witness find(ComparisonSet set, String title) {
StringBuilder sql = getSqlBuilder();
sql.append(" join juxta_comparison_set_member csm on csm.witness_id = w.id");
sql.append(" where csm.set_id = ? and w.name = ?");
return DataAccessUtils.uniqueResult( this.jdbcTemplate.query(sql.toString(),
new WitnessMapper(), set.getId(), title));
}
private StringBuilder getSqlBuilder() {
final StringBuilder sql = new StringBuilder();
sql.append("select w.id as w_id, w.name as w_name, w.created as w_created, w.updated as w_updated, ");
sql.append(" w.source_id as w_source_id, w.xslt_id as w_xslt_id,");
sql.append(" w.workspace_id as w_workspace_id, ");
sql.append( selectTextFrom("wt"));
sql.append(" from juxta_witness w");
sql.append(" join text_content wt on w.text_id = wt.id");
return sql;
}
@Override
public Witness find(Long id) {
StringBuilder sql = getSqlBuilder();
sql.append(" where w.id = ?");
return DataAccessUtils.uniqueResult(
this.jdbcTemplate.query(sql.toString(), new WitnessMapper(), id));
}
@Override
public void clearRevisions(Witness witness) {
final String sql = "delete from juxta_revision where witness_id=?";
this.jdbcTemplate.update(sql, witness.getId());
}
@Override
public void addRevisions(final List<RevisionInfo> revs) {
if ( revs.size() == 0 ) {
return;
}
StringBuilder sql = new StringBuilder();
sql.append("insert into juxta_revision");
sql.append(" (witness_id, revision_type, start, end, is_included, content)");
sql.append(" values (?,?,?,?,?,?)");
this.jdbcTemplate.batchUpdate(sql.toString(), new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setLong(1, revs.get(i).getWitnessId() );
ps.setString(2, revs.get(i).getType().toString());
ps.setLong(3, revs.get(i).getRange().getStart() );
ps.setLong(4, revs.get(i).getRange().getEnd());
ps.setBoolean(5, revs.get(i).isIncluded());
ps.setString(6, revs.get(i).getText());
}
@Override
public int getBatchSize() {
return revs.size();
}
} );
}
@Override
public boolean hasRevisions(final Witness witness) {
final String sql ="select count(*) as cnt from juxta_revision where witness_id=?";
int cnt = this.jdbcTemplate.queryForInt(sql, witness.getId());
return ( cnt > 0);
}
@Override
public List<RevisionInfo> getRevisions(final Witness witness) {
StringBuilder sql = new StringBuilder();
sql.append("select id,witness_id,revision_type,start,end,content,is_included");
sql.append(" from juxta_revision where witness_id=?");
return this.jdbcTemplate.query(sql.toString(), new RowMapper<RevisionInfo>(){
@Override
public RevisionInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
Range rng = new Range( rs.getLong("start"), rs.getLong("end"));
RevisionInfo.Type type = RevisionInfo.Type.valueOf( rs.getString("revision_type"));
RevisionInfo inf = new RevisionInfo( rs.getLong("id"), rs.getLong("witness_id"),
type, rng, rs.getString("content"), rs.getBoolean("is_included") );
return inf;
}}, witness.getId());
}
@Override
public List<Usage> getUsage(Witness witness) {
String setSql = "select distinct set_id,name from juxta_comparison_set_member " +
"inner join juxta_comparison_set on juxta_comparison_set.id = set_id " +
"where witness_id =?";
List<Usage> usage = this.jdbcTemplate.query(setSql, new RowMapper<Usage>(){
@Override
public Usage mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Usage(Usage.Type.COMPARISON_SET, rs.getLong("set_id"), rs.getString("name"));
}
},witness.getId());
// include the source (that we already know) that was used to generate this witness
String nmSql = "select name from juxta_source where id=?";
String srcName = this.jdbcTemplate.queryForObject(nmSql, String.class, witness.getSourceId());
usage.add( new Usage(Usage.Type.SOURCE, witness.getSourceId(), srcName));
return usage;
}
/**
* Map witness result set into model
* @author loufoster
*/
private static class WitnessMapper implements RowMapper<Witness> {
@Override
public Witness mapRow(ResultSet rs, int rowNum) throws SQLException {
final Witness witness = new Witness();
witness.setId(rs.getLong("w_id"));
witness.setName(rs.getString("w_name"));
witness.setSourceId( rs.getLong("w_source_id"));
Object templateIdObj = rs.getObject("w_xslt_id");
if (templateIdObj != null ) {
witness.setXsltId( (Long)templateIdObj );
}
witness.setWorkspaceId( rs.getLong("w_workspace_id") );
witness.setCreated( rs.getTimestamp("w_created") );
witness.setUpdated( rs.getTimestamp("w_updated") );
witness.setText( RelationalTextRepository.mapTextFrom(rs, "wt") );
return witness;
}
}
}