package org.juxtasoftware.dao.impl; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import org.juxtasoftware.dao.ComparisonSetDao; import org.juxtasoftware.dao.WitnessDao; import org.juxtasoftware.model.CollatorConfig; import org.juxtasoftware.model.CollatorConfig.HyphenationFilter; import org.juxtasoftware.model.ComparisonSet; import org.juxtasoftware.model.ComparisonSet.Status; import org.juxtasoftware.model.ResourceInfo; import org.juxtasoftware.model.Usage; import org.juxtasoftware.model.Witness; import org.juxtasoftware.model.Workspace; 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.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; /** * @author <a href="http://gregor.middell.net/" title="Homepage">Gregor Middell</a> */ @Repository public class ComparisionSetDaoImpl extends JuxtaDaoImpl<ComparisonSet> implements ComparisonSetDao { @Autowired private WitnessDao witnessDao; @Autowired private CacheDaoImpl cacheDao; @Autowired @Qualifier("executor") private TaskExecutor taskExecutor; private SimpleJdbcInsert memberInsert; protected SimpleJdbcInsert configInsert; protected SimpleJdbcInsert noteInsert; private static final SetRowMapper SET_ROW_MAPPER = new SetRowMapper(); private static final CollatorConfigRowMapper CFG_ROW_MAPPER = new CollatorConfigRowMapper(); private static final String CFG_TABLE = "juxta_collator_config"; private static final String SET_MEMBER_TABLE = "juxta_comparison_set_member"; private static final String NOTE_TABLE = "juxta_comparison_note"; public ComparisionSetDaoImpl() { super("juxta_comparison_set"); } @Override public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); this.memberInsert = new SimpleJdbcInsert(jt).withTableName( SET_MEMBER_TABLE ); this.configInsert = new SimpleJdbcInsert(jt).withTableName( CFG_TABLE ); this.noteInsert = new SimpleJdbcInsert(jt).withTableName( NOTE_TABLE ); } @Override public long create(ComparisonSet set) { Long id = super.create(set); set.setId( id ); CollatorConfig cfg = new CollatorConfig(); createCollatorConfig(id, cfg); return id; } @Override public ResourceInfo getInfo( final Long setId ) { 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_comparison_set w inner join juxta_workspace ws on workspace_id = ws.id where w.id=?"; return DataAccessUtils.uniqueResult( this.jt.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")); }}, setId)); } @Override public void clearCollationData( ComparisonSet set) { // be sure the set is always marked as not collated (unless its been deleted) if ( set.getStatus().equals(Status.DELETED) == false ) { set.setStatus(ComparisonSet.Status.NOT_COLLATED); update(set); } // clear alignments, annotations and cached visualization data this.cacheDao.deleteAll(set.getId()); while ( true ) { // NOTE: the alignments will cascade delete with the annotations // Also note: don't delete the manually created annotations final int maxDel = 5000; final String sql = "delete from juxta_annotation where set_id=? and manual=? order by id limit "+maxDel; int cnt = this.jt.update(sql, set.getId(), 0); if (cnt < maxDel ) { break; } try { Thread.sleep(50); } catch (InterruptedException e) {} } } @Override public void setTokenzedLength(ComparisonSet set, Witness witness, long tokenizedLength) { final String sql = "update "+SET_MEMBER_TABLE+" set tokenized_length=? where set_id=? and witness_id=?"; this.jt.update(sql, tokenizedLength, set.getId(), witness.getId()); } @Override public long getTokenzedLength(ComparisonSet set, Witness witness) { final String sql = "select tokenized_length from "+SET_MEMBER_TABLE+" where set_id=? and witness_id=?"; return this.jt.queryForLong(sql, set.getId(), witness.getId()); } @Override public List<Usage> getUsage(ComparisonSet set) { List<Witness> witnesses = getWitnesses(set); List<Usage> usage = new ArrayList<Usage>(); String nmSql = "select name from juxta_source where id=?"; Set<Long> srcIds = new HashSet<Long>(); for ( Witness w : witnesses) { usage.add( new Usage(Usage.Type.WITNESS, w.getId(), w.getName()) ); // don't add multiple copies of the same source! if ( srcIds.contains(w.getSourceId()) == false ) { srcIds.add( w.getSourceId()); String srcName = this.jt.queryForObject(nmSql, String.class, w.getSourceId()); usage.add( new Usage(Usage.Type.SOURCE, w.getSourceId(), srcName) ); } } return usage; } @Override public boolean exists(final Workspace ws, final String setName) { final String sql = "select count(*) as cnt from " + this.tableName +" where name = ? and workspace_id=?"; int cnt = this.jt.queryForInt(sql, setName, ws.getId() ); return (cnt > 0); } @Override public boolean hasAlignment(ComparisonSet set, Long alignmentId) { final String sql = "select count(*) as cnt from juxta_alignment where set_id=? and id=?"; int cnt = this.jt.queryForInt(sql, set.getId(), alignmentId); return (cnt > 0); } @Override public boolean isWitness(ComparisonSet set, Witness witness) { final String sql = "select count(*) as cnt from " + SET_MEMBER_TABLE + " where set_id = ? and witness_id=?"; int cnt = this.jt.queryForInt(sql, set.getId(), witness.getId()); return (cnt > 0); } @Override public void addWitnesses( final ComparisonSet set, final Set<Witness> witnesses) { if ( witnesses.size() > 0 ) { clearCollationData( set ); final long setId = set.getId(); final List<SqlParameterSource> batch = new ArrayList<SqlParameterSource>( witnesses.size()); for (Witness witness : witnesses ) { batch.add(new MapSqlParameterSource() .addValue("set_id", setId) .addValue("witness_id", witness.getId())); } this.memberInsert.executeBatch(batch.toArray(new SqlParameterSource[batch.size()])); updateLastUpdatedTime( set ); } } private void updateLastUpdatedTime(final ComparisonSet set) { final String sql = "update "+this.tableName+" set updated = ? where id = ?"; this.jt.update(sql, new Date(), set.getId()); } @Override public List<Witness> getWitnesses( final ComparisonSet set ) { return this.witnessDao.find( set ); } @Override public void deleteAllWitnesses(final ComparisonSet set) { this.jt.update("delete from "+SET_MEMBER_TABLE+" where set_id=?", set.getId()); set.setStatus(Status.NOT_COLLATED); update( set ); this.taskExecutor.execute(new Runnable() { @Override public void run() { clearCollationData(set); } }); } @Override public void deleteWitness(final ComparisonSet set, final Witness witness) { // delete witness and update time. These happen quickly. // Clearing of collation data can be LONG. put it in a worker final String sql = "delete from "+SET_MEMBER_TABLE+" where set_id=? and witness_id=?"; this.jt.update(sql, set.getId(), witness.getId() ); updateLastUpdatedTime( set ); set.setStatus(Status.NOT_COLLATED); update( set ); this.taskExecutor.execute(new Runnable() { @Override public void run() { clearCollationData(set); } }); } @Override public void update(final ComparisonSet set) { this.jt.update("update " + this.tableName + " set name = ?, status=?, updated=? where id = ?", set.getName(), set.getStatus().toString(), new Date(), set.getId()); } @Override public ComparisonSet find( final Workspace ws, final String setName) { final StringBuilder sql = new StringBuilder(); sql.append("select id, name, status, workspace_id, created, updated "); sql.append(" from "+this.tableName+" where name = ? and workspace_id=?"); ComparisonSet set = DataAccessUtils.uniqueResult(jt.query(sql.toString(), SET_ROW_MAPPER, setName, ws.getId())); return set; } @Override public ComparisonSet find(Long id) { final StringBuilder sql = new StringBuilder(); sql.append("select id, name, status, workspace_id, created, updated "); sql.append(" from "+this.tableName+" where id = ?"); ComparisonSet set = DataAccessUtils.uniqueResult(jt.query(sql.toString(), SET_ROW_MAPPER, id)); return set; } @Override protected SqlParameterSource toInsertData(ComparisonSet object) { final MapSqlParameterSource ps = new MapSqlParameterSource(); ps.addValue("name", object.getName()); ps.addValue("status", object.getStatus().toString()); ps.addValue("workspace_id", object.getWorkspaceId()); ps.addValue("created", new Date()); return ps; } @Override public void delete(final ComparisonSet set) { if ( set.getStatus().equals(Status.DELETED) == false ) { set.setName(set.getName()+"-DELETED-"+System.currentTimeMillis()); set.setStatus(Status.DELETED); update(set); this.taskExecutor.execute(new Runnable() { @Override public void run() { clearCollationData(set); jt.update("delete from " + tableName + " where id = ?", set.getId()); } }); } } @Override public List<ComparisonSet> list( final Workspace ws) { final String sql = "select id, name, status, workspace_id, created, updated from " +this.tableName+" where workspace_id=? and status!=? order by created desc"; return this.jt.query(sql, SET_ROW_MAPPER, ws.getId(), ComparisonSet.Status.DELETED.toString()); } @Override public CollatorConfig getCollatorConfig( final ComparisonSet set ) { final String sql = "select * from "+CFG_TABLE+" where set_id=?"; return DataAccessUtils.uniqueResult( this.jt.query(sql, CFG_ROW_MAPPER, set.getId())); } @Override public void updateCollatorConfig( final ComparisonSet set, final CollatorConfig cfg ) { final String sql = "delete from "+CFG_TABLE+" where set_id=?"; this.jt.update(sql, set.getId()); createCollatorConfig(set.getId(), cfg); updateLastUpdatedTime( set ); } private void createCollatorConfig( final Long setId, final CollatorConfig cfg ) { final MapSqlParameterSource ps = new MapSqlParameterSource(); ps.addValue("filter_case", cfg.isFilterCase() ); ps.addValue("filter_punctuation", cfg.isFilterPunctuation() ); ps.addValue("filter_whitespace", cfg.isFilterWhitespace() ); ps.addValue("hyphenation_filter", cfg.getHyphenationFilter().toString() ); ps.addValue("set_id", setId); this.configInsert.execute( ps ); } /** * Mapper class for comparison set * @author loufoster * */ private static class SetRowMapper implements RowMapper<ComparisonSet> { @Override public ComparisonSet mapRow(ResultSet rs, int rowNum) throws SQLException { final ComparisonSet cs = new ComparisonSet(); cs.setId(rs.getLong("id")); cs.setName(rs.getString("name")); cs.setStatus( rs.getString("status") ); cs.setWorkspaceId( rs.getLong("workspace_id")); cs.setCreated( rs.getTimestamp("created")); cs.setUpdated( rs.getTimestamp("updated")); return cs; } } /** * Mapper class for tokenizer config * @author loufoster * */ private static class CollatorConfigRowMapper implements RowMapper<CollatorConfig> { @Override public CollatorConfig mapRow(ResultSet rs, int rowNum) throws SQLException { final CollatorConfig cfg = new CollatorConfig(); cfg.setId( rs.getLong("id") ); cfg.setFilterCase( rs.getBoolean("filter_case") ); cfg.setFilterPunctuation( rs.getBoolean("filter_punctuation") ); cfg.setFilterWhitespace( rs.getBoolean("filter_whitespace") ); cfg.setHyphenationFilter( HyphenationFilter.valueOf(rs.getString("hyphenation_filter")) ); return cfg; } } }