Package org.apache.ctakes.ytex.uima.mapper

Source Code of org.apache.ctakes.ytex.uima.mapper.DocumentMapperServiceImpl$AnnoFSAttribute

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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.
*/
package org.apache.ctakes.ytex.uima.mapper;

import java.io.ByteArrayOutputStream;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.zip.GZIPOutputStream;

import javax.sql.DataSource;

import org.apache.commons.collections.map.CaseInsensitiveMap;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ctakes.ytex.dao.DBUtil;
import org.apache.ctakes.ytex.uima.model.Document;
import org.apache.ctakes.ytex.uima.model.DocumentAnnotation;
import org.apache.ctakes.ytex.uima.model.UimaType;
import org.apache.ctakes.ytex.uima.types.DocKey;
import org.apache.ctakes.ytex.uima.types.KeyValuePair;
import org.apache.uima.cas.FSIterator;
import org.apache.uima.cas.Feature;
import org.apache.uima.cas.FeatureStructure;
import org.apache.uima.cas.Type;
import org.apache.uima.cas.impl.XmiCasSerializer;
import org.apache.uima.cas.text.AnnotationIndex;
import org.apache.uima.jcas.JCas;
import org.apache.uima.jcas.cas.FSArray;
import org.apache.uima.jcas.cas.FSList;
import org.apache.uima.jcas.cas.NonEmptyFSList;
import org.apache.uima.jcas.cas.TOP;
import org.apache.uima.jcas.tcas.Annotation;
import org.apache.uima.util.XMLSerializer;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.hibernate.dialect.Dialect;
import org.hibernate.jdbc.Work;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.BiMap;
import com.google.common.collect.Collections2;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.SetMultimap;

/**
* Map document annotations to the database.
*
* @author vijay
*
*/
public class DocumentMapperServiceImpl implements DocumentMapperService,
    InitializingBean {
  /**
   * holder for FeatureStruct attributes
   *
   * @author vijay
   *
   */
  public static class AnnoFSAttribute {
    private int annoBaseId;

    private FeatureStructure fs;

    private Integer index;

    public AnnoFSAttribute() {
      super();
    }

    public AnnoFSAttribute(int annoBaseId, FeatureStructure fs,
        Integer index) {
      super();
      this.annoBaseId = annoBaseId;
      this.fs = fs;
      this.index = index;
    }

    public int getAnnoBaseId() {
      return annoBaseId;
    }

    public FeatureStructure getFs() {
      return fs;
    }

    public Integer getIndex() {
      return index;
    }

    public void setAnnoBaseId(int annoBaseId) {
      this.annoBaseId = annoBaseId;
    }

    public void setFs(FeatureStructure fs) {
      this.fs = fs;
    }

    public void setIndex(Integer index) {
      this.index = index;
    }

  }

  public static class AnnoLink {
    private int childAnnoBaseId;
    private String feature;
    private int parentAnnoBaseId;

    public AnnoLink(int annoId, int childAnnoId, String feature) {
      this.parentAnnoBaseId = annoId;
      this.childAnnoBaseId = childAnnoId;
      this.feature = feature;
    }

    public int getChildAnnoBaseId() {
      return childAnnoBaseId;
    }

    public String getFeature() {
      return feature;
    }

    public int getParentAnnoBaseId() {
      return parentAnnoBaseId;
    }

    public void setChildAnnoBaseId(int childAnnoBaseId) {
      this.childAnnoBaseId = childAnnoBaseId;
    }

    public void setFeature(String feature) {
      this.feature = feature;
    }

    public void setParentAnnoBaseId(int parentAnnoBaseId) {
      this.parentAnnoBaseId = parentAnnoBaseId;
    }
  }

  private static final Log log = LogFactory
      .getLog(DocumentMapperServiceImpl.class);

  private static Set<Integer> numericTypes = new HashSet<Integer>();
  private static Set<Integer> stringTypes = new HashSet<Integer>();
  /**
   * date format for analysis batch.
   */
  private static final ThreadLocal<DateFormat> tlAnalysisBatchDateFormat = new ThreadLocal<DateFormat>() {
    public DateFormat initialValue() {
      return new SimpleDateFormat("yyyy-MM-dd HH:mm");
    }
  };
  static {
    stringTypes.addAll(Arrays.asList(Types.CHAR, Types.NCHAR,
        Types.VARCHAR, Types.NVARCHAR));
    numericTypes.addAll(Arrays.asList(Types.BIGINT, Types.BIT,
        Types.BOOLEAN, Types.TINYINT, Types.SMALLINT, Types.DECIMAL,
        Types.FLOAT, Types.DOUBLE, Types.INTEGER));
  }
  private Set<AnnoMappingInfo> annoMappingInfos;
  private int batchSize = 100;
  private DataSource dataSource;
  private String dbSchema;
  private String dbType;
  private Dialect dialect;
  private String dialectClassName;

  private CaseInsensitiveMap docTableCols = new CaseInsensitiveMap();

  private String formattedTableName = null;

  private JdbcTemplate jdbcTemplate;

  private Map<String, AnnoMappingInfo> mapAnnoMappingInfo = new HashMap<String, AnnoMappingInfo>();
  private SessionFactory sessionFactory;

  private ThreadLocal<Map<String, AnnoMappingInfo>> tl_mapAnnoMappingInfo = new ThreadLocal<Map<String, AnnoMappingInfo>>() {

    @Override
    protected Map<String, AnnoMappingInfo> initialValue() {
      return new HashMap<String, AnnoMappingInfo>();
    }

  };

  /**
   * map of annotation to fields that need to be mapped
   */
  private ThreadLocal<SetMultimap<String, String>> tl_mapFieldInfo = new ThreadLocal<SetMultimap<String, String>>() {
    @Override
    protected SetMultimap<String, String> initialValue() {
      return HashMultimap.create();
    }

  };
  private PlatformTransactionManager transactionManager;
  private Map<String, UimaType> uimaTypeMap = new HashMap<String, UimaType>();
  private Properties ytexProperties;

  private void addAnnoLinks(JCas jcas,
      BiMap<Annotation, Integer> mapAnnoToId, List<AnnoLink> listAnnoLinks) {
    Collection<AnnoMappingInfo> annoLinkInfos = Collections2.filter(this
        .getMapAnnoMappingInfo().values(),
        new Predicate<AnnoMappingInfo>() {

          @Override
          public boolean apply(AnnoMappingInfo mi) {
            return "anno_link".equalsIgnoreCase(mi.getTableName());
          }
        });
    for (AnnoMappingInfo mi : annoLinkInfos) {
      addAnnoLinks(jcas, mapAnnoToId, listAnnoLinks, mi);
    }
  }

  private void addAnnoLinks(JCas jcas,
      BiMap<Annotation, Integer> mapAnnoToId,
      List<AnnoLink> listAnnoLinks, AnnoMappingInfo mi) {
    Type t = jcas.getTypeSystem().getType(mi.getAnnoClassName());
    if (t != null) {
      ColumnMappingInfo cip = mi.getMapField().get("parent_anno_base_id");
      ColumnMappingInfo cic = mi.getMapField().get("child_anno_base_id");
      // get the parent and child features
      Feature fp = t.getFeatureByBaseName(cip.getAnnoFieldName());
      Feature fc = t.getFeatureByBaseName(cic.getAnnoFieldName());
      // get all annotations
      FSIterator<FeatureStructure> iter = jcas.getFSIndexRepository()
          .getAllIndexedFS(t);
      while (iter.hasNext()) {
        FeatureStructure fs = iter.next();
        // get parent and child feature values
        FeatureStructure fsp = fs.getFeatureValue(fp);
        FeatureStructure fsc = fs.getFeatureValue(fc);
        if (fsp != null && fsc != null) {
          // extract the parent annotation from the parent feature
          // value
          Object parentAnno = extractFeature(cip.getJxpath(), fsp);
          if (parentAnno instanceof Annotation) {
            Integer parentId = mapAnnoToId
                .get((Annotation) parentAnno);
            if (parentId != null) {
              // parent is persisted, look for child(ren)
              if (fsc instanceof FSList || fsc instanceof FSArray) {
                // this is a one-to-many relationship
                // iterate over children
                List<FeatureStructure> children = extractList(fsc);
                for (FeatureStructure child : children) {
                  addLink(mapAnnoToId, listAnnoLinks,
                      t.getShortName(), cic.getJxpath(),
                      parentId, child);
                }
              } else {
                // this is a one-to-one relationship
                addLink(mapAnnoToId, listAnnoLinks,
                    t.getShortName(), cic.getJxpath(),
                    parentId, fsc);
              }
            }
          }
        }
      }
    }
  }

  /**
   * add a link. apply jxpath as needed, get child anno id, and save the link
   *
   * @param mapAnnoToId
   *            map to find existing annos
   * @param listAnnoLinks
   *            list to populate
   * @param linkType
   *            anno_link.feature
   * @param childJxpath
   *            jxpath to child annotation feature value, can be null
   * @param parentId
   *            parent_anno_base_id
   * @param child
   *            child object to apply jxpath to, or which is already an
   *            annotation
   */
  private void addLink(BiMap<Annotation, Integer> mapAnnoToId,
      List<AnnoLink> listAnnoLinks, String linkType, String childJxpath,
      Integer parentId, FeatureStructure child) {
    Object childAnno = extractFeature(childJxpath, child);
    if (childAnno instanceof Annotation) {
      Integer childId = mapAnnoToId.get((Annotation) childAnno);
      if (childId != null) {
        listAnnoLinks.add(new AnnoLink(parentId, childId, linkType));
      }
    }
  }

  /**
   * load the map of uima annotation class name to mapper class name from the
   * database.
   *
   * For some reason this is not getting executed within a transaction.
   * Manually wrap the db access in a transaction.
   *
   *
   * @throws Exception
   */
  @SuppressWarnings("unchecked")
  public void afterPropertiesSet() {
    TransactionTemplate txTemplate = new TransactionTemplate(
        this.getTransactionManager());
    txTemplate
        .setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
    txTemplate.execute(new TransactionCallback<Object>() {

      @Override
      public Object doInTransaction(TransactionStatus arg0) {
        Query q = getSessionFactory().getCurrentSession()
            .getNamedQuery("getUimaTypes");
        List<UimaType> uimaTypes = q.list();
        for (UimaType uimaType : uimaTypes) {
          uimaTypeMap.put(uimaType.getUimaTypeName(), uimaType);
        }
        initDocKeyMapping();
        return null;
      }
    });
  }

  private Document createDocument(JCas jcas, String analysisBatch,
      boolean bStoreDocText, boolean bStoreCAS) {
    Document doc = new Document();
    if (bStoreDocText)
      doc.setDocText(jcas.getDocumentText());
    doc.setAnalysisBatch(analysisBatch == null
        || analysisBatch.length() == 0 ? getDefaultAnalysisBatch()
        : analysisBatch);
    // look for the ctakes DocumentID anno
    if (setUimaDocId(jcas, doc,
        "edu.mayo.bmi.uima.core.type.structured.DocumentID",
        "documentID") == null) {
      // look for the uima SourceDocumentInformation anno
      setUimaDocId(jcas, doc,
          "org.apache.uima.examples.SourceDocumentInformation", "uri");
    }
    // look for document
    if (bStoreCAS) {
      try {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        GZIPOutputStream zipOut = new GZIPOutputStream(out);
        XmiCasSerializer ser = new XmiCasSerializer(
            jcas.getTypeSystem());
        XMLSerializer xmlSer = new XMLSerializer(zipOut, false);
        ser.serialize(jcas.getCas(), xmlSer.getContentHandler());
        zipOut.close();
        doc.setCas(out.toByteArray());
      } catch (Exception saxException) {
        log.error("error serializing document cas", saxException);
      }
    }
    return doc;
  }

  private void extractAndSaveDocKey(JCas jcas, Document doc) {
    AnnotationIndex<Annotation> idx = jcas
        .getAnnotationIndex(DocKey.typeIndexID);
    FSIterator<Annotation> annoIterator = idx.iterator();
    if (annoIterator.hasNext())
      this.saveDocKey(doc, (DocKey) annoIterator.next());
  }

  /**
   * apply jxpath to object
   *
   * @param jxpath
   * @param child
   * @return child if jxpath null, else apply jxpath
   */
  private Object extractFeature(String jxpath, Object child) {
    return jxpath != null ? JXPathContext.newContext(child)
        .getValue(jxpath) : child;
  }

  /**
   * covert a FSArray or FSList into a List<FeatureStructure>
   *
   * @param fsc
   * @return list, entries guaranteed not null
   */
  private List<FeatureStructure> extractList(FeatureStructure fsc) {
    List<FeatureStructure> listFS = new ArrayList<FeatureStructure>();
    if (fsc != null) {
      if (fsc instanceof FSArray) {
        FSArray fsa = (FSArray) fsc;
        for (int i = 0; i < fsa.size(); i++) {
          FeatureStructure fsElement = fsa.get(i);
          if (fsElement != null)
            listFS.add(fsElement);
        }
      } else if (fsc instanceof FSList) {
        FSList fsl = (FSList) fsc;
        while (fsl instanceof NonEmptyFSList) {
          FeatureStructure fsElement = ((NonEmptyFSList) fsl)
              .getHead();
          if (fsElement != null)
            listFS.add(fsElement);
          fsl = ((NonEmptyFSList) fsl).getTail();
        }
      }
    }
    return listFS;
  }

  public Set<AnnoMappingInfo> getAnnoMappingInfos() {
    return annoMappingInfos;
  }

  public int getBatchSize() {
    return batchSize;
  }

  public DataSource getDataSource() {
    return jdbcTemplate.getDataSource();
  }

  public String getDbSchema() {
    return dbSchema;
  }

  public String getDbType() {
    return dbType;
  }

  private String getDefaultAnalysisBatch() {
    return tlAnalysisBatchDateFormat.get().format(new Date());
  }

  public String getDialectClassName() {
    return dialectClassName;
  }

  public Map<String, AnnoMappingInfo> getMapAnnoMappingInfo() {
    return mapAnnoMappingInfo;
  }

  private AnnoMappingInfo getMapInfo(FeatureStructure fs) {
    Type type = fs.getType();
    String className = type.getName();
    // if the key is there, then return it (may be null)
    AnnoMappingInfo mapInfo = null;
    if (this.tl_mapAnnoMappingInfo.get().containsKey(className)) {
      mapInfo = this.tl_mapAnnoMappingInfo.get().get(className);
    } else {
      // load the mappinginfo, save in cache
      mapInfo = initMapInfo(fs);
      this.tl_mapAnnoMappingInfo.get().put(className, mapInfo);
    }
    return mapInfo;
  }

  public SessionFactory getSessionFactory() {
    return sessionFactory;
  }

  private String getTablePrefix() {
    String tablePrefix = "";
    if ("mssql".equals(dbType)) {
      tablePrefix = dbSchema + ".";
    }
    return tablePrefix;
  }

  public PlatformTransactionManager getTransactionManager() {
    return transactionManager;
  }

  public Properties getYtexProperties() {
    return ytexProperties;
  }

  public void initDocKeyMapping() {
    AbstractEntityPersister cm = (AbstractEntityPersister) this.sessionFactory
        .getClassMetadata(Document.class);
    // figure out which columns are already mapped
    String[] propNames = cm.getPropertyNames();
    Set<String> mappedCols = new TreeSet<String>(
        String.CASE_INSENSITIVE_ORDER);
    for (String prop : propNames) {
      String cols[] = cm.getPropertyColumnNames(prop);
      mappedCols.addAll(Arrays.asList(cols));
    }
    // this.formattedTableName = DBUtil.formatTableName(cm.getTableName());
    this.formattedTableName = cm.getTableName();
    log.info("document table name = " + formattedTableName);
    final String query = "select * from " + formattedTableName
        + " where 1=2";
    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;
    try {
      conn = dataSource.getConnection();
      stmt = conn.createStatement();
      rs = stmt.executeQuery(query);
      ResultSetMetaData rsmd = rs.getMetaData();
      int nCols = rsmd.getColumnCount();
      for (int i = 1; i <= nCols; i++) {
        String colName = rsmd.getColumnName(i);
        if (!mappedCols.contains(colName)) {
          log.info("document candidate foreign key column: "
              + colName);
          docTableCols.put(colName, rsmd.getColumnType(i));
        }
      }
      if (log.isDebugEnabled()) {
        log.debug("docTableCols: " + docTableCols);
      }
    } catch (SQLException e) {
      log.error("problem determining document table fields", e);
      throw new RuntimeException(e);
    } finally {
      try {
        if (rs != null)
          rs.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        if (stmt != null)
          stmt.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        if (conn != null)
          conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }

    }
  }

  /**
   * load mapping info
   *
   * @param type
   * @return
   */
  private AnnoMappingInfo initMapInfo(final FeatureStructure fs) {
    final Type type = fs.getType();
    final String annoName = type.getShortName().toLowerCase();
    AnnoMappingInfo mapInfoTmp;
    final UimaType ut = uimaTypeMap.get(type.getName());
    if (this.mapAnnoMappingInfo.containsKey(type.getName())) {
      mapInfoTmp = this.mapAnnoMappingInfo.get(type.getName()).deepCopy();
    } else {
      mapInfoTmp = new AnnoMappingInfo();
    }
    final AnnoMappingInfo mapInfo = mapInfoTmp;
    if (ut != null)
      mapInfo.setUimaTypeId(ut.getUimaTypeID());
    // first see if the table name has been set in beans-uima.xml
    if (Strings.isNullOrEmpty(mapInfo.getTableName())) {
      // next see if the table name has been set in ref_uima_type
      if (ut != null && !Strings.isNullOrEmpty(ut.getTableName()))
        mapInfo.setTableName(ut.getTableName());
      else
        // default to anno_[short name]
        mapInfo.setTableName("anno_" + annoName);
    }
    final List<Feature> features = type.getFeatures();
    // get the non primitive fields
    for (Feature f : features) {
      if (f.getRange().isArray()
          && !f.getRange().getComponentType().isPrimitive()) {
        // add this field to the list of fields to store
        this.tl_mapFieldInfo.get()
            .put(type.getName(), f.getShortName());
      }
    }
    this.sessionFactory.getCurrentSession().doWork(new Work() {
      @Override
      public void execute(Connection conn) throws SQLException {
        ResultSet rs = null;

        try {
          DatabaseMetaData dmd = conn.getMetaData();
          // get columns for corresponding table
          // mssql - add schema prefix
          // oracle - convert table name to upper case
          rs = dmd.getColumns(
              null,
              "mssql".equals(dbType) || "hsql".equals(dbType) ? dbSchema
                  : null,
              "orcl".equals(dbType) || "hsql".equals(dbType) ? mapInfo
                  .getTableName().toUpperCase() : mapInfo
                  .getTableName(), null);
          while (rs.next()) {
            String colName = rs.getString("COLUMN_NAME");
            int colSize = rs.getInt("COLUMN_SIZE");
            int dataType = rs.getInt("DATA_TYPE");
            if ("anno_base_id".equalsIgnoreCase(colName)) {
              // skip anno_base_id
              continue;
            }
            if ("uima_type_id".equalsIgnoreCase(colName)) {
              // see if there is a uima_type_id column
              // for FeatureStructures that are not annotations
              // there can be a field for the uima_type_id
              if (!(fs instanceof Annotation)
                  && Strings.isNullOrEmpty(mapInfo
                      .getUimaTypeIdColumnName())) {
                mapInfo.setUimaTypeIdColumnName(colName);
              }
            } else if ("coveredText".equalsIgnoreCase(colName)) {
              // see if there is a coveredText column, store the
              // covered
              // text here
              ColumnMappingInfo coveredTextColumn = new ColumnMappingInfo();
              coveredTextColumn.setColumnName(colName);
              mapInfo.setCoveredTextColumn(coveredTextColumn);
              coveredTextColumn.setSize(colSize);
            } else {
              // possibility 1: the column is already mapped to
              // the field
              // if so, then just set the size
              if (!updateSize(mapInfo, colName, colSize, dataType)) {
                // possibility 2: the column is not mapped - see
                // if
                // it matches a field
                // iterate through features, see which match the
                // column
                for (Feature f : features) {
                  String annoFieldName = f.getShortName();
                  if (f.getRange().isPrimitive()
                      && annoFieldName
                          .equalsIgnoreCase(colName)) {
                    // primitive attribute
                    ColumnMappingInfo fmap = new ColumnMappingInfo();
                    fmap.setAnnoFieldName(annoFieldName);
                    fmap.setColumnName(colName);
                    fmap.setSize(colSize);
                    fmap.setSqlType(dataType);
                    mapInfo.getMapField()
                        .put(colName, fmap);
                    break;
                  } else if (!f.getRange().isArray()
                      && !f.getRange().isPrimitive()
                      && annoFieldName
                          .equalsIgnoreCase(colName)
                      && (dataType == Types.INTEGER
                          || dataType == Types.SMALLINT
                          || dataType == Types.BIGINT
                          || dataType == Types.NUMERIC
                          || dataType == Types.FLOAT || dataType == Types.DOUBLE)) {
                    // this feature is a reference to
                    // another
                    // annotation.
                    // this column is numeric - a match
                    ColumnMappingInfo fmap = new ColumnMappingInfo();
                    fmap.setAnnoFieldName(annoFieldName);
                    fmap.setColumnName(colName);
                    fmap.setSize(colSize);
                    fmap.setSqlType(dataType);
                    mapInfo.getMapField()
                        .put(colName, fmap);
                    break;
                  }
                }
              }
            }
          }
        } finally {
          if (rs != null) {
            try {
              rs.close();
            } catch (SQLException e) {
            }
          }
        }
      }
    });
    // don't map this annotation if no fields match columns
    if (mapInfo.getMapField().size() == 0
        && mapInfo.getCoveredTextColumn() == null
        && Strings.isNullOrEmpty(mapInfo.getUimaTypeIdColumnName()))
      return null;
    // generate sql
    StringBuilder b = new StringBuilder("insert into ");
    b.append(this.getTablePrefix()).append(mapInfo.getTableName());
    b.append("(anno_base_id");
    // add coveredText column if available
    if (mapInfo.getCoveredTextColumn() != null) {
      b.append(", coveredText");
    }
    // add uima_type_id column if available
    if (mapInfo.getUimaTypeIdColumnName() != null) {
      b.append(", uima_type_id");
    }
    // add other fields
    for (Map.Entry<String, ColumnMappingInfo> fieldEntry : mapInfo
        .getMapField().entrySet()) {
      b.append(", ").append(dialect.openQuote())
          .append(fieldEntry.getValue().getColumnName())
          .append(dialect.closeQuote());
    }
    b.append(") values (?");
    // add coveredText bind param
    if (mapInfo.getCoveredTextColumn() != null) {
      b.append(", ?");
    }
    // add uimaTypeId bind param
    if (mapInfo.getUimaTypeIdColumnName() != null) {
      b.append(", ?");
    }
    // add bind params for other fields
    b.append(Strings.repeat(", ?", mapInfo.getMapField().size())).append(
        ")");
    mapInfo.setSql(b.toString());
    if (log.isInfoEnabled())
      log.info("sql insert for type " + type.getName() + ": "
          + mapInfo.getSql());
    if (log.isDebugEnabled())
      log.debug("initMapInfo(" + annoName + "): " + mapInfo);
    return mapInfo;
  }

  /**
   * insert annotation containment links.
   *
   * @param documentId
   */
  private void insertAnnotationContainmentLinks(int documentId) {
    if (log.isTraceEnabled())
      log.trace("begin insertAnnotationContainmentLinks");
    Query q = sessionFactory.getCurrentSession().getNamedQuery(
        "insertAnnotationContainmentLinks");
    q.setInteger("documentID", documentId);
    q.executeUpdate();
    if (log.isTraceEnabled())
      log.trace("end insertAnnotationContainmentLinks");
  }

  private BiMap<Annotation, Integer> saveAnnoBase(final JCas jcas,
      final Set<String> setTypesToIgnore, final int docId) {
    final AnnotationIndex<Annotation> annoIdx = jcas
        .getAnnotationIndex(Annotation.typeIndexID);
    final List<Annotation> listAnno = new ArrayList<Annotation>(
        annoIdx.size());
    final BiMap<Annotation, Integer> mapAnnoToId = HashBiMap.create();
    final FSIterator<Annotation> annoIterator = annoIdx.iterator();
    this.sessionFactory.getCurrentSession().doWork(new Work() {

      @Override
      public void execute(Connection conn) throws SQLException {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
          ps = conn
              .prepareStatement(
                  "insert into "
                      + getTablePrefix()
                      + "anno_base (document_id, span_begin, span_end, uima_type_id) values (?, ?, ?, ?)",
                  Statement.RETURN_GENERATED_KEYS);
          while (annoIterator.hasNext()) {
            Annotation anno = (Annotation) annoIterator.next();
            String annoClass = anno.getClass().getName();
            if (!setTypesToIgnore.contains(annoClass)
                && uimaTypeMap.containsKey(annoClass)) {
              // should not ignore, and we know how to map this
              // annotation
              listAnno.add(anno);
              ps.setInt(1, docId);
              ps.setInt(2, anno.getBegin());
              ps.setInt(3, anno.getEnd());
              ps.setInt(4, uimaTypeMap.get(annoClass)
                  .getUimaTypeID());
              ps.addBatch();
            }
          }
          ps.executeBatch();
          rs = ps.getGeneratedKeys();
          int annoIndex = 0;
          while (rs.next()) {
            mapAnnoToId.put(listAnno.get(annoIndex), rs.getInt(1));
            annoIndex++;
          }
        } catch (SQLException e) {
          throw new RuntimeException(e);
        } finally {
          if (rs != null) {
            try {
              rs.close();
            } catch (SQLException e) {
            }
          }
          if (ps != null) {
            try {
              ps.close();
            } catch (SQLException e) {
            }
          }
        }
      }
    });
    return mapAnnoToId;
  }

  private BiMap<Annotation, Integer> saveAnnoBaseHib(JCas jcas,
      Set<String> setTypesToIgnore, Document doc) {
    if (log.isTraceEnabled())
      log.trace("begin saveAnnoBaseHib");
    AnnotationIndex<Annotation> annoIdx = jcas
        .getAnnotationIndex(Annotation.typeIndexID);
    List<Annotation> listAnno = new ArrayList<Annotation>(annoIdx.size());
    Map<Annotation, DocumentAnnotation> mapAnnoToHib = new HashMap<Annotation, DocumentAnnotation>();
    FSIterator<Annotation> annoIterator = annoIdx.iterator();
    int count = 0;
    // iterate over annotations and save them
    while (annoIterator.hasNext()) {
      Annotation anno = (Annotation) annoIterator.next();
      String annoClass = anno.getClass().getName();
      if (!setTypesToIgnore.contains(annoClass)
          && this.uimaTypeMap.containsKey(annoClass)) {
        // should not ignore, and we know how to map this annotation
        listAnno.add(anno);
        DocumentAnnotation hibAnno = new DocumentAnnotation();
        hibAnno.setDocument(doc);
        hibAnno.setBegin(anno.getBegin());
        hibAnno.setEnd(anno.getEnd());
        hibAnno.setUimaType(uimaTypeMap.get(annoClass));
        sessionFactory.getCurrentSession().save(hibAnno);
        if (++count % batchSize == 0)
          sessionFactory.getCurrentSession().flush();
        doc.getDocumentAnnotations().add(hibAnno);
        mapAnnoToHib.put(anno, hibAnno);
      }
    }
    sessionFactory.getCurrentSession().flush();
    BiMap<Annotation, Integer> mapAnnoToId = HashBiMap.create();
    for (Map.Entry<Annotation, DocumentAnnotation> e : mapAnnoToHib
        .entrySet()) {
      mapAnnoToId.put(e.getKey(), e.getValue().getDocumentAnnotationID());
    }
    if (log.isTraceEnabled())
      log.trace("end saveAnnoBaseHib");
    return mapAnnoToId;
  }

  /**
   * bind the variables to the prepared statement
   *
   * @param type
   * @param mapInfo
   * @param ps
   * @param annoId
   * @param anno
   * @throws SQLException
   */
  private void saveAnnoBindVariables(final Type type,
      final AnnoMappingInfo mapInfo, PreparedStatement ps, int annoId,
      FeatureStructure anno, final BiMap<Annotation, Integer> mapAnnoToId)
      throws SQLException {
    // set anno_base_id
    int argIdx = 1;
    ps.setInt(argIdx++, annoId);
    if (mapInfo.getCoveredTextColumn() != null) {
      String trunc = null;
      if (anno instanceof Annotation) {
        trunc = truncateString(((Annotation) anno).getCoveredText(),
            mapInfo.getCoveredTextColumn().getSize());
      }
      ps.setString(argIdx++, trunc);
    }
    if (!Strings.isNullOrEmpty(mapInfo.getUimaTypeIdColumnName())) {
      ps.setInt(argIdx++, mapInfo.getUimaTypeId());
    }
    // iterate over fields
    for (Map.Entry<String, ColumnMappingInfo> fieldEntry : mapInfo
        .getMapField().entrySet()) {
      ColumnMappingInfo fieldMapInfo = fieldEntry.getValue();
      String fieldName = fieldMapInfo.getAnnoFieldName();
      Feature feat = type.getFeatureByBaseName(fieldName);
      if (fieldMapInfo.getConverter() != null) {
        try {
          String prop = anno.getFeatureValueAsString(feat);
          ps.setObject(
              argIdx,
              fieldMapInfo.getConverter().convert(
                  fieldMapInfo.getTargetType(), prop));
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      } else if (!feat.getRange().isPrimitive()) {
        // feature is a structure/annotation
        FeatureStructure fs = anno.getFeatureValue(feat);
        if (fs == null) {
          // feature is null - set the column to null
          ps.setNull(argIdx, fieldMapInfo.getSqlType());
        } else {
          if (fieldMapInfo.getJxpath() != null) {
            // jxpath to pull out feature attribute
            Object o = this.extractFeature(
                fieldMapInfo.getJxpath(), fs);
            if (o == null) {
              // extracted value null - set column to null
              ps.setNull(argIdx, fieldMapInfo.getSqlType());
            } else if (o instanceof String) {
              // string - truncate as needed
              String trunc = truncateString((String) o,
                  fieldMapInfo.getSize());
              ps.setString(argIdx, trunc);
            } else {
              // set value
              ps.setObject(argIdx, o);
            }
          } else {
            // reference to another annotation - get the other
            // anno's id
            Integer refAnnoId = null;
            if (fs instanceof Annotation) {
              refAnnoId = mapAnnoToId.get(fs);
            }
            if (refAnnoId != null) {
              ps.setInt(argIdx, refAnnoId);
            } else {
              ps.setNull(argIdx, Types.INTEGER);
            }
          }
        }
      } else {
        if ("uima.cas.Integer".equals(feat.getRange().getName())) {
          ps.setInt(argIdx, anno.getIntValue(feat));
        } else if ("uima.cas.Short".equals(feat.getRange().getName())) {
          ps.setShort(argIdx, anno.getShortValue(feat));
        } else if ("uima.cas.Long".equals(feat.getRange().getName())) {
          ps.setLong(argIdx, anno.getLongValue(feat));
        } else if ("uima.cas.Float".equals(feat.getRange().getName())) {
          ps.setFloat(argIdx, anno.getFloatValue(feat));
        } else if ("uima.cas.Double".equals(feat.getRange().getName())) {
          ps.setDouble(argIdx, anno.getDoubleValue(feat));
        } else if ("uima.cas.Byte".equals(feat.getRange().getName())) {
          ps.setByte(argIdx, anno.getByteValue(feat));
        } else if ("uima.cas.Boolean".equals(feat.getRange().getName())) {
          ps.setBoolean(argIdx, anno.getBooleanValue(feat));
        } else if ("uima.cas.String".equals(feat.getRange().getName())) {
          String trunc = truncateString(anno.getStringValue(feat),
              fieldMapInfo.getSize());
          ps.setString(argIdx, trunc);
        }
      }
      argIdx++;
    }
  }

  /**
   * insert composite attributes.
   *
   * @param listFSA
   */
  private void saveAnnoFS(final List<AnnoFSAttribute> listFSA,
      final BiMap<Annotation, Integer> mapAnnoToId) {
    if (listFSA.size() == 0)
      return;
    FeatureStructure fs = listFSA.get(0).getFs();
    final Type type = fs.getType();
    final AnnoMappingInfo mapInfo = this.getMapInfo(fs);
    // don't know how to map this feature
    if (mapInfo == null)
      return;
    // int chunks = (int) Math.ceil((double) listFSA.size()
    // / (double) this.batchSize);
    // for (int i = 0; i < chunks; i++) {
    // int start = i * this.batchSize;
    // int end = (i + 1) * this.batchSize;
    // if (end > listFSA.size())
    // end = listFSA.size();
    // final List<AnnoFSAttribute> chunkList = listFSA.subList(start, end);
    // jdbcTemplate.batchUpdate(mapInfo.getSql(),
    // new BatchPreparedStatementSetter() {
    //
    // @Override
    // public int getBatchSize() {
    // return chunkList.size();
    // }
    //
    // @Override
    // public void setValues(PreparedStatement ps, int idx)
    // throws SQLException {
    // AnnoFSAttribute fsa = chunkList.get(idx);
    // // todo pass array index for storage
    // saveAnnoBindVariables(type, mapInfo, ps,
    // fsa.getAnnoBaseId(), fsa.getFs(),
    // mapAnnoToId);
    // }
    // });
    // }
    chunkedBatchUpdate(mapInfo.getSql(), listFSA,
        new ChunkPreparedStatementSetter<AnnoFSAttribute>() {

          @Override
          public void setValues(PreparedStatement ps, int idx,
              AnnoFSAttribute fsa) throws SQLException {
            // todo pass array index for storage
            saveAnnoBindVariables(type, mapInfo, ps,
                fsa.getAnnoBaseId(), fsa.getFs(), mapAnnoToId);
          }
        });
  }

  /**
   * @see #chunkedBatchUpdate
   * @author vijay
   *
   * @param <T>
   */
  public static interface ChunkPreparedStatementSetter<T> {
    public abstract void setValues(PreparedStatement ps, int idx, T record)
        throws SQLException;
  }

  /**
   * for the list l, perform l.size()/batchSize batch updates. Avoid mysql
   * packet too large exceptions with large batch updates. Call spring
   * jdbcTemplate.batchUpdate internally with sublists of l with size
   * batchSize.
   *
   * @param sql
   * @param l
   * @param cpss
   */
  private <T> void chunkedBatchUpdate(String sql, List<T> l,
      final ChunkPreparedStatementSetter<T> cpss) {
    int chunks = (int) Math.ceil((double) l.size()
        / (double) this.batchSize);
    for (int i = 0; i < chunks; i++) {
      int start = i * this.batchSize;
      int end = (i + 1) * this.batchSize;
      if (end > l.size())
        end = l.size();
      final List<T> chunkList = l.subList(start, end);
      jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {

        @Override
        public int getBatchSize() {
          return chunkList.size();
        }

        @Override
        public void setValues(PreparedStatement ps, int idx)
            throws SQLException {
          T record = chunkList.get(idx);
          cpss.setValues(ps, idx, record);
        }
      });
    }
  }

  /**
   * save annotation to annotation links (many-to-many relationships)
   *
   * @param listAnnoLinks
   */
  private void saveAnnoLinks(final List<AnnoLink> listAnnoLinks) {
    if (log.isTraceEnabled())
      log.trace("begin saveAnnoLinks");
    // jdbcTemplate
    // .batchUpdate(
    // "insert into "
    // + this.getTablePrefix()
    // +
    // "anno_link(parent_anno_base_id, child_anno_base_id, feature) values (?, ?, ?)",
    // new BatchPreparedStatementSetter() {
    //
    // @Override
    // public int getBatchSize() {
    // return listAnnoLinks.size();
    // }
    //
    // @Override
    // public void setValues(PreparedStatement ps, int idx)
    // throws SQLException {
    // AnnoLink l = listAnnoLinks.get(idx);
    // ps.setInt(1, l.getParentAnnoBaseId());
    // ps.setInt(2, l.getChildAnnoBaseId());
    // ps.setString(3, l.getFeature());
    // }
    // });
    chunkedBatchUpdate(
        "insert into "
            + this.getTablePrefix()
            + "anno_link(parent_anno_base_id, child_anno_base_id, feature) values (?, ?, ?)",
        listAnnoLinks, new ChunkPreparedStatementSetter<AnnoLink>() {

          @Override
          public void setValues(PreparedStatement ps, int idx,
              AnnoLink l) throws SQLException {
            ps.setInt(1, l.getParentAnnoBaseId());
            ps.setInt(2, l.getChildAnnoBaseId());
            ps.setString(3, l.getFeature());
          }
        });
    if (log.isTraceEnabled())
      log.trace("end saveAnnoLinks");
  }

  /**
   * save the annotation properties for a given type
   *
   * @param mapIdToAnno
   *            map of all annoIDs to Annotation
   * @param annoIds
   *            annotation ids for a single type
   * @param listAnnoLinks
   *            annotation to annotation links to save
   */
  private void saveAnnoPrimitive(
      final BiMap<Annotation, Integer> mapAnnoToId,
      final Set<Integer> annoIds, final List<AnnoLink> listAnnoLinks) {
    if (log.isTraceEnabled())
      log.trace("begin saveAnnoPrimitive");
    final BiMap<Integer, Annotation> mapIdToAnno = mapAnnoToId.inverse();
    // nothing to do
    if (annoIds.size() == 0)
      return;
    // covert to array for spring batch update
    // final Integer[] annoIdArray = annoIds.toArray(new Integer[] {});
    final List<Integer> annoIdList = new ArrayList<Integer>(annoIds);
    // get mappinginfo
    // final TOP t = mapIdToAnno.get(annoIdArray[0]);
    final TOP t = mapIdToAnno.get(annoIdList.get(0));
    final Type type = t.getType();
    final AnnoMappingInfo mapInfo = this.getMapInfo(t);
    // get non primitive fields, insert them after inserting the annotation
    final Set<String> fsNames = this.tl_mapFieldInfo.get().get(
        type.getName());
    final ListMultimap<String, AnnoFSAttribute> mapAnnoToFS = ArrayListMultimap
        .create();
    // don't know how to map this annotation
    if (mapInfo == null)
      return;
    // jdbcTemplate.batchUpdate(mapInfo.getSql(),
    // new BatchPreparedStatementSetter() {
    //
    // @Override
    // public int getBatchSize() {
    // return annoIdArray.length;
    // }
    this.chunkedBatchUpdate(mapInfo.getSql(), annoIdList,
        new ChunkPreparedStatementSetter<Integer>() {

          @Override
          public void setValues(PreparedStatement ps, int idx,
              Integer annoId) throws SQLException {
            // get the entry
            // int annoId = annoIdArray[idx];
            Annotation anno = mapIdToAnno.get(annoId);
            saveAnnoBindVariables(type, mapInfo, ps, annoId, anno,
                mapAnnoToId);
            // pull out the composite fields for storage
            for (String fieldName : fsNames) {
              Feature feat = type.getFeatureByBaseName(fieldName);
              if (!feat.getRange().isPrimitive()) {
                // handle arrays and lists
                FeatureStructure fsCol = anno
                    .getFeatureValue(feat);
                if (fsCol != null
                    && (fsCol instanceof FSArray || fsCol instanceof FSList)) {
                  List<FeatureStructure> fsList = extractList(fsCol);
                  int i = 0;
                  for (FeatureStructure fs : fsList) {
                    if (fs instanceof Annotation) {
                      // annotations are linked via the
                      // anno_link table
                      Integer childAnnoId = mapAnnoToId
                          .get(fs);
                      if (childAnnoId != null) {
                        listAnnoLinks.add(new AnnoLink(
                            annoId, childAnnoId,
                            feat.getShortName()));
                      }
                    } else {
                      // featureStructs that are not
                      // annotations get stored in their
                      // own tables
                      // with a many to one relationship
                      // to the annotation
                      mapAnnoToFS.put(fs.getType()
                          .getName(),
                          new AnnoFSAttribute(annoId,
                              fs, i++));
                    }
                  }
                }
              } else {
                // handle primitive attributes
                mapAnnoToFS.put(
                    feat.getRange().getName(),
                    new AnnoFSAttribute(annoId, anno
                        .getFeatureValue(feat), null));
              }
            }
          }
        }

    );
    for (String fsType : mapAnnoToFS.keySet()) {
      this.saveAnnoFS(mapAnnoToFS.get(fsType), mapAnnoToId);
    }
    if (log.isTraceEnabled())
      log.trace("end saveAnnoPrimitive");
  }

  // private void saveAnnotations(JCas jcas, Set<String> setTypesToIgnore,
  // int documentId) {
  // BiMap<Annotation, Integer> mapAnnoToId = saveAnnoBase(jcas,
  // setTypesToIgnore, documentId);
  // // split the annotations up by type
  // // create a map of class name to anno id
  // SetMultimap<String, Integer> mapTypeToAnnoId = HashMultimap.create();
  // for (Map.Entry<Annotation, Integer> annoEntry : mapAnnoToId.entrySet()) {
  // mapTypeToAnnoId.put(annoEntry.getKey().getClass().getName(),
  // annoEntry.getValue());
  // }
  // // allocate a list to store annotation links
  // List<AnnoLink> listAnnoLinks = new ArrayList<AnnoLink>();
  // // save annotation properties
  // for (String annoClass : mapTypeToAnnoId.keySet()) {
  // saveAnnoPrimitive(mapAnnoToId, mapTypeToAnnoId.get(annoClass),
  // listAnnoLinks);
  // }
  // addAnnoLinks(jcas, mapAnnoToId, listAnnoLinks);
  // // saveMarkablePairs(jcas, mapAnnoToId, listAnnoLinks);
  // // saveCoref(jcas, mapAnnoToId, listAnnoLinks);
  // saveAnnoLinks(listAnnoLinks);
  // }

  private void saveAnnotationsHib(JCas jcas,
      boolean bInsertAnnotationContainmentLinks,
      Set<String> setTypesToIgnore, Document doc) {
    if (log.isTraceEnabled())
      log.trace("begin saveAnnotationsHib");
    BiMap<Annotation, Integer> mapAnnoToId = saveAnnoBaseHib(jcas,
        setTypesToIgnore, doc);
    if (bInsertAnnotationContainmentLinks)
      insertAnnotationContainmentLinks(doc.getDocumentID());
    // split the annotations up by type
    // create a map of class name to anno id
    SetMultimap<String, Integer> mapTypeToAnnoId = HashMultimap.create();
    for (Map.Entry<Annotation, Integer> annoEntry : mapAnnoToId.entrySet()) {
      mapTypeToAnnoId.put(annoEntry.getKey().getClass().getName(),
          annoEntry.getValue());
    }
    // allocate a list to store annotation links
    List<AnnoLink> listAnnoLinks = new ArrayList<AnnoLink>();
    // save annotation properties
    for (String annoClass : mapTypeToAnnoId.keySet()) {
      saveAnnoPrimitive(mapAnnoToId, mapTypeToAnnoId.get(annoClass),
          listAnnoLinks);
    }
    addAnnoLinks(jcas, mapAnnoToId, listAnnoLinks);
    // saveMarkablePairs(jcas, mapAnnoToId, listAnnoLinks);
    // saveCoref(jcas, mapAnnoToId, listAnnoLinks);
    saveAnnoLinks(listAnnoLinks);
    if (log.isTraceEnabled())
      log.trace("end saveAnnotationsHib");
  }

  /**
   * update the document table - set key values from dockey for the give
   * document_id
   *
   * @param document
   *            document
   * @param dk
   *            key
   */
  private void saveDocKey(Document document, DocKey dk) {
    int documentId = document.getDocumentID();
    FSArray fsa = dk.getKeyValuePairs();
    if (fsa == null || fsa.size() == 0)
      return;
    // build query dynamically
    StringBuilder queryBuilder = (new StringBuilder("update ")).append(
        formattedTableName).append(" set ");
    List<Object> args = new ArrayList<Object>();
    boolean bFirstArg = true;
    // iterate over key/value pairs
    for (int i = 0; i < fsa.size(); i++) {
      KeyValuePair kp = (KeyValuePair) fsa.get(i);
      String key = kp.getKey();
      if (key.equalsIgnoreCase("instance_id")) {
        // instance_id is something we 'know' about - set it
        document.setInstanceID(kp.getValueLong());
      } else if (key.equalsIgnoreCase("instance_key")) {
        document.setInstanceKey(kp.getValueString());
      } else if (this.docTableCols.containsKey(key)) {
        // only attempt to map keys that correspond to valid columns
        boolean badArg = false;
        // verify that the value matches the datatype
        // if valueString not null then assume integer
        if (kp.getValueString() != null
            && stringTypes.contains(docTableCols.get(key))) {
          args.add(kp.getValueString());
        } else if (numericTypes.contains(docTableCols.get(key))) {
          args.add(kp.getValueLong());
        } else {
          // invalid type for argument
          badArg = true;
          log.warn("document_id: " + documentId
              + ", bad type for key=" + key + ", value="
              + kp.getValueString() == null ? kp.getValueLong()
              : kp.getValueString());
        }
        if (!badArg) {
          // update
          if (!bFirstArg) {
            queryBuilder.append(", ");
          }
          queryBuilder.append(DBUtil.formatFieldName(key));
          queryBuilder.append("=? ");
          bFirstArg = false;
        }
      } else {
        // don't know what to do with this key attribute
        log.warn("document_id: " + documentId
            + ", could not map key attribute " + kp.getKey());
      }
    }
    if (args.size() > 0) {
      // have something to update - add the where condition
      queryBuilder.append(" where document_id = ?");
      args.add(documentId);
      String sql = queryBuilder.toString();
      if (log.isDebugEnabled()) {
        log.debug(sql);
      }
      jdbcTemplate.update(sql, args.toArray());
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see
   * ytex.dao.mapper.DocumentMapperService#saveDocument(org.apache.uima.jcas
   * .JCas, java.lang.String)
   */
  public Integer saveDocument(final JCas jcas, final String analysisBatch,
      final boolean bStoreDocText, final boolean bStoreCAS,
      final boolean bInsertAnnotationContainmentLinks,
      final Set<String> setTypesToIgnore) {
    if (log.isTraceEnabled())
      log.trace("begin saveDocument");
    // communicate options to mappers using thread local variable
    final DefaultTransactionDefinition txDef = new DefaultTransactionDefinition(
        TransactionDefinition.PROPAGATION_REQUIRES_NEW);
    txDef.setIsolationLevel("orcl".equals(this.dbType) ? TransactionDefinition.ISOLATION_READ_COMMITTED
        : TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
    final TransactionTemplate txTemplate = new TransactionTemplate(
        this.getTransactionManager(), txDef);
    final int documentId = txTemplate
        .execute(new TransactionCallback<Integer>() {

          @Override
          public Integer doInTransaction(TransactionStatus arg0) {
            Document doc = createDocument(jcas, analysisBatch,
                bStoreDocText, bStoreCAS);
            sessionFactory.getCurrentSession().save(doc);
            // make sure the document has been saved
            getSessionFactory().getCurrentSession().flush();
            saveAnnotationsHib(jcas,
                bInsertAnnotationContainmentLinks,
                setTypesToIgnore, doc);
            extractAndSaveDocKey(jcas, doc);
            return doc.getDocumentID();
          }
        });
    if (log.isTraceEnabled())
      log.trace("end saveDocument");
    return documentId;
  }

  /**
   * initialize mapAnnoMappingInfo from the set
   *
   * @param annoMappingInfos
   */
  public void setAnnoMappingInfos(Set<AnnoMappingInfo> annoMappingInfos) {
    this.annoMappingInfos = annoMappingInfos;
    for (AnnoMappingInfo mi : annoMappingInfos) {
      this.mapAnnoMappingInfo.put(mi.getAnnoClassName(), mi);
    }
  }

  public void setBatchSize(int batchSize) {
    this.batchSize = batchSize;
  }

  public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
    jdbcTemplate = new JdbcTemplate(dataSource);
  }

  public void setDbSchema(String dbSchema) {
    this.dbSchema = dbSchema;
  }

  public void setDbType(String dbType) {
    this.dbType = dbType;
  }

  public void setDialectClassName(String dialectClassName) {
    this.dialectClassName = dialectClassName;
    try {
      this.dialect = (Dialect) Class.forName(dialectClassName)
          .newInstance();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  public void setMapAnnoMappingInfo(
      Map<String, AnnoMappingInfo> mapAnnoMappingInfo) {
    this.mapAnnoMappingInfo = mapAnnoMappingInfo;
  }

  public void setSessionFactory(SessionFactory sessionFactory) {
    this.sessionFactory = sessionFactory;
  }

  public void setTransactionManager(
      PlatformTransactionManager transactionManager) {
    this.transactionManager = transactionManager;
  }

  /**
   * get the document id from the specified type and feature.
   *
   * @param jcas
   * @param doc
   * @param idType
   * @param idFeature
   * @return docId if found, else null
   */
  private String setUimaDocId(JCas jcas, Document doc, String idType,
      String idFeature) {
    Type docIDtype = jcas.getTypeSystem().getType(idType);
    Feature docIDFeature = null;
    if (docIDtype != null)
      docIDFeature = docIDtype.getFeatureByBaseName(idFeature);
    if (docIDtype != null && docIDFeature != null) {
      // AnnotationIndex<Annotation> idx = jcas
      // .getAnnotationIndex(docIDtype);
      FSIterator<FeatureStructure> iter = jcas.getFSIndexRepository()
          .getAllIndexedFS(docIDtype);
      if (iter != null) {
        if (iter.hasNext()) {
          FeatureStructure docId = iter.next();
          String uimaDocId = docId.getStringValue(docIDFeature);
          if (!Strings.isNullOrEmpty(uimaDocId)) {
            uimaDocId = this.truncateString(uimaDocId, 256);
            doc.setInstanceKey(uimaDocId);
            return uimaDocId;
          }
        }
      }
    }
    return null;
  }

  public void setYtexProperties(Properties ytexProperties) {
    this.ytexProperties = ytexProperties;
  }

  private String truncateString(String val, int size) {
    String trunc = val;
    if (!Strings.isNullOrEmpty(val) && val.length() > size) {
      trunc = val.substring(0, size);
    }
    return trunc;
  }

  /**
   * update column size for given column, if the column has been mapped
   *
   * @param mapInfo
   * @param colName
   * @param colSize
   * @return true column is mapped to a field
   */
  private boolean updateSize(AnnoMappingInfo mapInfo, String colName,
      int colSize, int sqlType) {
    ColumnMappingInfo fi = mapInfo.getMapField().get(colName);
    if (fi != null) {
      fi.setSqlType(sqlType);
      if (fi.getSize() <= 0)
        fi.setSize(colSize);
      return true;
    }
    return false;
  }

}
TOP

Related Classes of org.apache.ctakes.ytex.uima.mapper.DocumentMapperServiceImpl$AnnoFSAttribute

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.