/**
* 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;
}
}