package org.juxtasoftware.dao.impl; import java.io.IOException; import java.io.Reader; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Date; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.stream.XMLStreamException; import org.codehaus.stax2.XMLInputFactory2; import org.juxtasoftware.dao.SourceDao; import org.juxtasoftware.model.ResourceInfo; import org.juxtasoftware.model.Source; import org.juxtasoftware.model.Usage; 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.dao.support.DataAccessUtils; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; import com.google.common.base.Strings; import eu.interedition.text.Text; import eu.interedition.text.TextRepository; import eu.interedition.text.rdbms.RelationalText; import eu.interedition.text.rdbms.RelationalTextRepository; import eu.interedition.text.util.SQL; import eu.interedition.text.xml.XML; @Repository public class SourceDaoImpl implements SourceDao, InitializingBean { @Autowired private TextRepository textRepository; @Autowired private JdbcTemplate jdbcTemplate; @Autowired private LuceneHelper lucene; private SimpleJdbcInsert insert; private static final String TABLE_NAME = "juxta_source"; private static final XMLInputFactory2 XML_INPUT_FACTORY = XML.createXMLInputFactory(); @Override public void afterPropertiesSet() throws Exception { this.insert = new SimpleJdbcInsert(jdbcTemplate).withTableName(TABLE_NAME).usingGeneratedKeyColumns("id"); } @Override public Long create(final Workspace ws, final String name, final Source.Type type, Reader contentReader) throws IOException, XMLStreamException { Text txtContent = null; if ( type.equals(Source.Type.XML)) { txtContent = this.textRepository.create(XML_INPUT_FACTORY.createXMLStreamReader(contentReader)); } else { txtContent = this.textRepository.create(contentReader); } final MapSqlParameterSource ps = new MapSqlParameterSource(); ps.addValue("name", name); ps.addValue("content_id", ((RelationalText) txtContent).getId()); ps.addValue("content_type", type.toString()); ps.addValue("workspace_id", ws.getId()); ps.addValue("created", new Date()); Long srcId = (Long)this.insert.executeAndReturnKey( ps ); // add the new source to the lucene index Long textId = ((RelationalText)txtContent).getId(); Reader r = getContentReader( textId ); this.lucene.addDocument("source", ws.getName(), srcId, name, textId, r); return srcId; } private String getWorkspaceName(Long id) { String sql = "select name from juxta_workspace where id=?"; return this.jdbcTemplate.queryForObject(sql, String.class, id); } @Override public ResourceInfo getInfo( final Long sourceId ) { 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_source 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")); }}, sourceId)); } @Override public void update(final Source src, final String newName) { String sql = "update juxta_source set name=?, updated=? where id=?"; this.jdbcTemplate.update(sql, newName, new Date(), src.getId()); } @Override public void update( Source src, final String newName, final Reader contentReader) throws IOException, XMLStreamException { // create a new text entry for this source Text txtContent = null; if ( src.getType().equals(Source.Type.XML) ) { txtContent = this.textRepository.create(XML_INPUT_FACTORY.createXMLStreamReader(contentReader)); } else { txtContent = this.textRepository.create(contentReader); } Long contentId = ((RelationalText) txtContent).getId(); Long oldContentID = ((RelationalText)src.getText()).getId(); src.setName(newName); src.setText(txtContent); String sql = "update juxta_source set name=?, content_id=?, updated=? where id=?"; this.jdbcTemplate.update(sql, newName, contentId, new Date(), src.getId() ); // delete the old content! this.jdbcTemplate.update("delete from text_content where id=?", oldContentID); // Update the index: remove the old and add the updted src as new String ws = getWorkspaceName(src.getWorkspaceId()); this.lucene.deleteDocument(oldContentID); this.lucene.addDocument("source", ws, src.getId(), src.getName(), contentId, getContentReader(contentId) ); } @Override public Reader getContentReader( final Source src ) { return getContentReader( ((RelationalText)src.getText()).getId() ); } private Reader getContentReader( final Long textId ) { 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"); }}, textId )); } @Override public String getRootElement(Source src) { if ( src.getType().equals(Source.Type.XML) == false ) { return null; } final String sql = "select content from text_content where id=?"; String tag = this.jdbcTemplate.queryForObject(sql, new RowMapper<String>() { @Override public String mapRow(ResultSet rs, int rowNum) throws SQLException { try { return findRoolElement(rs.getCharacterStream("content")); } catch (IOException e) { return null; } }}, ((RelationalText)src.getText()).getId()); return tag; } private String findRoolElement(Reader reader) throws IOException { char[] buff = new char[64]; Pattern patt = Pattern.compile("<[^?!]"); StringBuffer tag = new StringBuffer(); while ( true ) { int cnt = reader.read(buff); if ( cnt == -1) { break; } else { String data = new String(buff); if ( tag.length() == 0 ) { Matcher m = patt.matcher( data ); if ( m.find() ) { int p1 = m.start(); int p2 = data.indexOf('>', p1); if ( p2 > -1 ) { tag.append( data.substring(p1+1, p2) ); break; } else { tag.append( data.substring(p1+1) ); } } } else { int pos = data.indexOf('>'); if ( pos > -1 ) { tag.append( data.substring(0, pos) ); break; } else { tag.append( data ); } } } } int pos = tag.indexOf(" "); if ( pos > -1 ) { return tag.substring(0,pos); } return tag.toString(); } @Override public void delete(Source obj) { this.lucene.deleteDocument( ((RelationalText)obj.getText()).getId() ); this.jdbcTemplate.update("delete from "+TABLE_NAME+" where id=?", obj.getId()); this.textRepository.delete(obj.getText()); } @Override public List<Source> list( final Workspace ws) { return jdbcTemplate.query(buildFinderSQL("where workspace_id=? order by created desc"), ROW_MAPPER, ws.getId()); } @Override public boolean exists(final Workspace ws, final String name) { List<Source> sources = jdbcTemplate.query(buildFinderSQL("where s.name = ?"), ROW_MAPPER, name); boolean exists = false; for ( Source src : sources ) { if ( src.isMemberOf(ws)) { exists = true; break; } } return exists; } @Override public Source find(final Long workspaceId, final Long id) { return DataAccessUtils.uniqueResult( this.jdbcTemplate.query(buildFinderSQL("where s.workspace_id=? and s.id = ?"), ROW_MAPPER, workspaceId, id)); } @Override public Source find(final Long workspaceId, final String name) { return DataAccessUtils.uniqueResult( this.jdbcTemplate.query(buildFinderSQL("where s.workspace_id=? and s.name = ?"), ROW_MAPPER, workspaceId, name)); } @Override public List<Usage> getUsage(Source src) { // First pass, find all witnesses that have been created from this source final String witnessSql = "select id,name from juxta_witness where source_id=?"; List<Usage> usage = this.jdbcTemplate.query(witnessSql, new RowMapper<Usage>() { @Override public Usage mapRow(ResultSet rs, int rowNum) throws SQLException { return new Usage(Usage.Type.WITNESS, rs.getLong("id"), rs.getString("name")); } }, src.getId()); // find all of the sets that use these witnesses. Add these to the initial list if ( usage.size() > 0 ) { StringBuilder ids = new StringBuilder(); for (Usage u : usage) { if ( ids.length() > 0) { ids.append(","); } ids.append( u.getId() ); } 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 in ("+ids+")"; usage.addAll( 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")); } })); } return usage; } @Override public String makeUniqueName(final Workspace ws, final String origName) { final String sql = "select `name` from juxta_source where `name` like ?"; int maxNum = 0; List<String> names = this.jdbcTemplate.queryForList(sql, String.class, origName+"-%"); for (String name : names ) { String numStr = name.substring(origName.length()+1); if ( numStr.length() > 0 ) { try { int num = Integer.parseInt(numStr); if ( num > maxNum ) { maxNum = num; } } catch (Exception e) {} } } return origName+"-"+(maxNum+1); } protected String buildFinderSQL(String whereClause) { final StringBuilder sql = new StringBuilder("select "); sql.append(selectSourceFrom("s")).append(", "); sql.append(RelationalTextRepository.selectTextFrom("t")); sql.append(" from juxta_source s"); sql.append(" join text_content t on s.content_id = t.id"); if (!Strings.isNullOrEmpty(whereClause)) { sql.append(" ").append(whereClause); } return sql.toString(); } public static String selectSourceFrom(String tableName) { return SQL.select(tableName, "id", "name", "content_type", "workspace_id", "created"); } public static Source mapSourceFrom(ResultSet rs, String prefix, Text text) throws SQLException { final Source source = new Source(); source.setId(rs.getLong(prefix + "_id")); source.setName(rs.getString(prefix + "_name")); source.setType(rs.getString(prefix + "_content_type")); source.setWorkspaceId( rs.getLong(prefix+"_workspace_id")); source.setCreated( rs.getTimestamp(prefix+"_created")); source.setText(text); return source; } private static final RowMapper<Source> ROW_MAPPER = new RowMapper<Source>() { @Override public Source mapRow(ResultSet rs, int rowNum) throws SQLException { return mapSourceFrom(rs, "s", RelationalTextRepository.mapTextFrom(rs, "t")); } }; }