package org.juxtasoftware.dao.impl; import java.io.IOException; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import org.juxtasoftware.Constants; import org.juxtasoftware.dao.AlignmentDao; import org.juxtasoftware.dao.WitnessDao; import org.juxtasoftware.model.Alignment; import org.juxtasoftware.model.Alignment.AlignedAnnotation; import org.juxtasoftware.model.AlignmentConstraint; import org.juxtasoftware.model.ComparisonSet; import org.juxtasoftware.model.Witness; import org.juxtasoftware.util.FragmentFormatter; import org.juxtasoftware.util.RangedTextReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; 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.Name; import eu.interedition.text.Range; import eu.interedition.text.rdbms.RelationalName; @Repository public class AlignmentDaoImpl implements AlignmentDao, InitializingBean { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private WitnessDao witnessDao; private static final String TABLE_NAME = "juxta_alignment"; private static final int FRAG_SIZE = 30; private static final int DEL_FRAG_SIZE = 45; private SimpleJdbcInsert jdbcInsert; protected static final Logger LOG = LoggerFactory.getLogger( Constants.WS_LOGGER_NAME ); @Override public void afterPropertiesSet() throws Exception { this.jdbcInsert = new SimpleJdbcInsert(this.jdbcTemplate).withTableName(TABLE_NAME); } @Override public int create( final List<Alignment> alignments) { final List<SqlParameterSource> psBatch = new ArrayList<SqlParameterSource>(); for (Alignment align : alignments) { psBatch.add(toSqlParameterSource(align)); } int[] rowsAffected = this.jdbcInsert.executeBatch(psBatch.toArray(new SqlParameterSource[psBatch.size()])); return rowsAffected.length; } protected SqlParameterSource toSqlParameterSource(Alignment align) { final MapSqlParameterSource ps = new MapSqlParameterSource(); ps.addValue("set_id", align.getComparisonSetId() ); ps.addValue("qname_id", ((RelationalName)align.getName()).getId() ); ps.addValue("group_num", align.getGroup() ); ps.addValue("manual", align.isManual() ); ps.addValue("edit_distance", align.getEditDistance() ); List<AlignedAnnotation> annos = align.getAnnotations(); ps.addValue("annotation_a_id", annos.get(0).getId() ); ps.addValue("annotation_b_id", annos.get(1).getId() ); return ps; } @Override public void delete(final Long id) { final String sql = "delete from "+TABLE_NAME+" where id=?"; this.jdbcTemplate.update(sql, id); } @Override public List<Alignment> list(final AlignmentConstraint constraint ) { // MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); // memoryBean.gc(); // MemoryUsage usage = memoryBean.getHeapMemoryUsage(); // long freeMem = usage.getMax() -usage.getUsed(); // LOG.info("["+ freeMem +"] FREE MEMORY"); // basic query for alignmnents in set StringBuilder sql = alignmentAnnotationsQuery(); sql.append(" where a.set_id = ").append(constraint.getSetId()); // qName filtering if ( constraint.getFilter() != null ) { sql.append(" and aqn.id in ("); int cnt = 0; for ( Name qname : constraint.getFilter().getQNames() ) { if ( cnt > 0){ sql.append(","); } cnt++; sql.append( ((RelationalName)qname).getId() ); } sql.append(")"); } // add sql to handle witness filtering based on mode addWitnessFilterConstraints( sql, constraint ); // sort order sql.append(" order by max_start, min_start asc"); if ( constraint.isResultsRangeSet() ) { sql.append(" limit ").append(constraint.getFrom()).append(",").append(constraint.getBatchSize()); } // get the list of alignments that involve the base witness AlignmentsMapper mapper = new AlignmentsMapper( constraint ); this.jdbcTemplate.query(sql.toString(), mapper ); return mapper.getAlignments(); // usage = memoryBean.getHeapMemoryUsage(); // long freeMem2 = usage.getMax() -usage.getUsed(); // if ( a.size() > 0 ) { // LOG.info("["+ freeMem2 +"] FREE AFTER ALIGN LISTED. ALIGN COUNT: "+a.size()); // double avg = (freeMem - freeMem2) / a.size(); // LOG.info("AVERAGE ALIGNMENT SIZE: "+avg); // } // return a; } private void addWitnessFilterConstraints(StringBuilder sql, AlignmentConstraint constraint) { // turn the witness filter into a set StringBuilder idSet = new StringBuilder(); if ( constraint.getWitnessIdFilter().size() > 0 ) { idSet.append("("); for ( Long id : constraint.getWitnessIdFilter() ) { if (idSet.length() > 1) { idSet.append(","); } idSet.append(id); } idSet.append(")"); } // for non-baseless queries, make sure the base is one of the witnesse in annotations if ( constraint.isBaseless() == false ) { Long bID = constraint.getBaseId(); sql.append( " and (a1.witness_id=").append(bID).append(" or a2.witness_id=").append(bID).append(") " ); // in BASED mode, the filter is EXCLUSIVE. Do not return alignments with // annotataions on witnesses from the list. if ( constraint.getWitnessIdFilter().size() > 0 ) { sql.append(" and a1.witness_id not in ").append(idSet).append(" and a2.witness_id not in ").append(idSet); } } else { // In Baseless mode, the witness filter is INCLUSIVE. Only return alignments that // have annotations on witnesses that are included in the filter if ( constraint.getWitnessIdFilter().size() > 0 ) { sql.append(" and a1.witness_id in ").append(idSet).append(" and a2.witness_id in ").append(idSet); } } } @Override public Long count(AlignmentConstraint constraint) { StringBuilder sql = countAnnotationsQuery(); sql.append(" where a.set_id = ").append(constraint.getSetId()); if ( constraint.getFilter() != null ) { sql.append(" and aqn.id in ("); int cnt = 0; for ( Name qname : constraint.getFilter().getQNames() ) { if ( cnt > 0){ sql.append(","); } cnt++; sql.append( ((RelationalName)qname).getId() ); } sql.append(")"); } addWitnessFilterConstraints(sql, constraint); return this.jdbcTemplate.queryForLong(sql.toString()); } private final StringBuilder countAnnotationsQuery( ) { StringBuilder sb = new StringBuilder(); sb.append("select count(*) as cnt "); sb.append(" from ").append(TABLE_NAME).append(" as a "); sb.append(" inner join text_qname as aqn on a.qname_id=aqn.id "); sb.append(" inner join juxta_annotation as a1 on a1.id=a.annotation_a_id "); sb.append(" inner join juxta_annotation as a2 on a2.id=a.annotation_b_id "); return sb; } /** * Gener query to get all annotations for all alignments. * Use this as a basis for all others * @return */ private final StringBuilder alignmentAnnotationsQuery( ) { StringBuilder sb = new StringBuilder(); sb.append("select a.id as a_id, a.set_id as a_set_id, a.edit_distance as edit_distance, "); sb.append(" a.group_num as group_num, a.manual as manual, "); sb.append(" aqn.id as aqn_id, aqn.namespace as namespace, aqn.local_name as local_name, "); sb.append(" a1.witness_id as w1_id, a1.id as a1_id, a1.range_start as a1_start, a1.range_end as a1_end, "); sb.append(" a1_qn.id as a1_qn_id, a1_qn.namespace as a1_namespace, a1_qn.local_name as a1_local_name, "); sb.append(" a2.witness_id as w2_id, a2.id as a2_id, a2.range_start as a2_start, a2.range_end as a2_end, "); sb.append(" a2_qn.id as a2_qn_id, a2_qn.namespace as a2_namespace, a2_qn.local_name as a2_local_name, "); sb.append(" LEAST(a1.range_start, a2.range_start) as min_start, "); sb.append(" GREATEST(a1.range_start, a2.range_start) as max_start "); sb.append(" from ").append(TABLE_NAME).append(" as a "); sb.append(" inner join text_qname as aqn on a.qname_id=aqn.id "); sb.append(" inner join juxta_annotation as a1 on a1.id=a.annotation_a_id "); // annotation #1 sb.append(" inner join text_qname as a1_qn on a1.qname_id=a1_qn.id "); sb.append(" inner join juxta_annotation as a2 on a2.id=a.annotation_b_id "); // annotation #2 sb.append(" inner join text_qname as a2_qn on a2.qname_id=a2_qn.id "); return sb; } @Override public Alignment find(final ComparisonSet set, final Long id) { StringBuilder sql = alignmentAnnotationsQuery(); sql.append(" where a.id = ").append(id); AlignmentConstraint constraint = new AlignmentConstraint(set); AlignmentsMapper mapper = new AlignmentsMapper(constraint ); this.jdbcTemplate.query(sql.toString(), mapper ); if (mapper.getAlignments().isEmpty() ) { return null; } else { Alignment alignment = mapper.getAlignments().get(0); getFragments( alignment ); return alignment; } } private void getFragments( Alignment align) { for (AlignedAnnotation a : align.getAnnotations()) { // generate a range that has the alignment as its center // and extends away for some context Range origRange = new Range( a.getRange() ); Range witnessRange = new Range( a.getRange() ); Witness witness = this.witnessDao.find(a.getWitnessId()); long start = witnessRange.getStart()-FRAG_SIZE; if ( witnessRange.length() == 0 ) { start = witnessRange.getStart()-DEL_FRAG_SIZE; } start = Math.max(0, start); long witnessLen = witness.getText().getLength(); long end = Math.min(witnessLen, witnessRange.getEnd()+FRAG_SIZE); String frag; try { // read the full fragment final RangedTextReader reader = new RangedTextReader(); reader.read( this.witnessDao.getContentStream(witness), new Range(start, end) ); frag = reader.toString(); } catch (IOException e) { // couldn't get fragment. skip it for now return; } a.setFragment( FragmentFormatter.format(frag, origRange, new Range(start,end), witness.getText().getLength() )); } } /** * Mapper for a series of alignments * @author loufoster * */ private static class AlignmentsMapper implements RowMapper<Void> { private List<Alignment> alignments = new ArrayList<Alignment>(); private final AlignmentConstraint constraint; public AlignmentsMapper(AlignmentConstraint constraint) { this.constraint = constraint; } public List<Alignment> getAlignments() { return this.alignments; } @Override public Void mapRow(ResultSet rs, int rowNum) throws SQLException { // grab some constraint data for readability Range tgtRange = this.constraint.getRange(); // create a new alignment with all data except annotations Alignment align = new Alignment(); align.setId( rs.getLong("a_id") ); RelationalName qname = new RelationalName(rs.getString("namespace"), rs.getString("local_name"), rs.getLong("aqn_id")); align.setName( qname ); if ( rs.getBoolean("manual")) { align.setManual(); } align.setComparisonSetId( this.constraint.getSetId() ); align.setEditDistance( rs.getInt("edit_distance") ); align.setGroup( rs.getInt("group_num") ); // collect annotation info. Toss the entire thing if constraints apply for (int i=1;i<=2;i++) { Long annoId = rs.getLong("a"+i+"_id"); Long witnessId = rs.getLong("w"+i+"_id"); Range range = new Range(rs.getInt("a"+i+"_start"), rs.getInt("a"+i+"_end")); if ( tgtRange != null ) { if ( this.constraint.isBaseless() || witnessId.equals( this.constraint.getBaseId() ) ) { if ( range.getStart() < tgtRange.getStart() || range.getEnd() > tgtRange.getEnd() ) { return null; } } } RelationalName qn = new RelationalName( rs.getString("a"+i+"_namespace"), rs.getString("a"+i+"_local_name"), rs.getLong("a"+i+"_qn_id")); align.addAnnotation( new AlignedAnnotation(qn, witnessId, annoId, range)); } // If we got here, itsa keeper this.alignments.add(align); return null; } } }