/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.transformation.ui.wizards.xmlfile;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.ByteOrderMark;
import org.apache.commons.io.input.BOMInputStream;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.teiid.core.designer.util.CoreArgCheck;
import org.teiid.core.designer.util.I18nUtil;
import org.teiid.core.designer.util.StringConstants;
import org.teiid.core.designer.util.StringUtilities;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.query.IProcedureService;
import org.teiid.designer.query.IQueryService;
import org.teiid.designer.query.proc.ITeiidXmlColumnInfo;
import org.teiid.designer.query.proc.ITeiidXmlFileInfo;
import org.teiid.designer.transformation.ui.UiConstants;
import org.teiid.designer.transformation.ui.wizards.file.TeiidFileInfo;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.LocatorImpl;
/**
* Business object used to manage Teiid-specific XML Data File information used during import
*
*
* @since 8.0
*/
public class TeiidXmlFileInfo extends TeiidFileInfo implements UiConstants, ITeiidXmlFileInfo<TeiidXmlColumnInfo> {
private static final String I18N_PREFIX = I18nUtil.getPropertyPrefix(TeiidXmlFileInfo.class);
private static final String XSI_NAMESPACE_PREFIX = "xsi"; //$NON-NLS-1$
private static String getString( final String id ) {
return Util.getString(I18N_PREFIX + id);
}
private static String getString( final String id, final Object arg1) {
return Util.getString(I18N_PREFIX + id, arg1);
}
/** UTF-8 BOM */
public static final ByteOrderMark UTF_8 = new ByteOrderMark("UTF-8", 0xEF, 0xBB, 0xBF);
/** UTF-16BE BOM (Big Endian) */
public static final ByteOrderMark UTF_16BE = new ByteOrderMark("UTF-16BE", 0xFE, 0xFF);
/** UTF-16LE BOM (Little Endian) */
public static final ByteOrderMark UTF_16LE = new ByteOrderMark("UTF-16LE", 0xFF, 0xFE);
public static final ByteOrderMark UTF_32BE = new ByteOrderMark("UTF-32BE", 0x00, 0x00, 0xFE, 0xFF);
/** UTF-16LE BOM (Little Endian) */
public static final ByteOrderMark UTF_32LE = new ByteOrderMark("UTF-32LE", 0x00, 0x00, 0xFF, 0xFE);
/**
* The number of cached lines of the data file for display
*/
private int numberOfCachedLines = 20;
/**
* The number of lines in data file
*/
private int numberOfLinesInFile = 0;
/**
* An initial xquery root path expression
*
* XMLTABLE([<NSP>,] xquery-expression [<PASSING>] [COLUMNS <COLUMN>, ... )] AS name
*
* Usually of the form '$d/MedlineCitationSet/MedlineCitation'. In this case, the expression defines the initial path
* inside the XML structure that the COLUMN PATH's are relative to
*/
private String rootPath = StringConstants.EMPTY_STRING;
/**
* Common Root path
*/
private String commonRootPath = StringConstants.EMPTY_STRING;
/**
* Indicator for the import processor to attempt to create a View Table given the info in this object.
*/
private boolean doProcess;
/**
* The cached <code>Collection</code> of the first 6 lines to use for UI display purposes
*/
private String[] cachedFirstLines;
/**
* The <code>List</code> of <code>TeiidXmlColumnInfo</code> objects parsed from the defined header information.
*/
private List<TeiidXmlColumnInfo> columnInfoList;
private Map<String, Object> parameterMap = new HashMap<String,Object>();
private XmlElement rootNode;
private Map<String, String> namespaceMap;
private IStatus parsingStatus;
private String xmlFileUrl;
private boolean isUrl = false;
/**
*
* @param dataFile the Teiid-formatted data file
*/
public TeiidXmlFileInfo(File dataFile) {
super(dataFile, false);
CoreArgCheck.isNotNull(dataFile, "dataFile is null"); //$NON-NLS-1$
initialize();
}
/**
*
* @param info the data file info object
*/
public TeiidXmlFileInfo(TeiidXmlFileInfo info) {
super(info.getDataFile(), false);
inject(info);
}
/**
* This method allows setting the values in the current info object using the values from an external info object
*
* @param info the data file info object
*/
public void inject(TeiidXmlFileInfo info) {
CoreArgCheck.isNotNull(info.getDataFile(), "dataFile is null"); //$NON-NLS-1$
this.cachedFirstLines = info.cachedFirstLines;
this.numberOfLinesInFile = info.getNumberOfLinesInFile();
this.rootPath = info.getRootPath();
this.parameterMap = new HashMap<String, Object>(parameterMap);
this.columnInfoList = new ArrayList<TeiidXmlColumnInfo>();
for( ITeiidXmlColumnInfo iColInfo : info.getColumnInfoList() ) {
TeiidXmlColumnInfo colInfo = (TeiidXmlColumnInfo) iColInfo;
this.columnInfoList.add(new TeiidXmlColumnInfo(
colInfo.getXmlElement(),
colInfo.getXmlAttribute(),
colInfo.getSymbolName(),
colInfo.getOrdinality(),
colInfo.getDatatype(),
colInfo.getDefaultValue(),
getRootPath(),
colInfo.getFullXmlPath()));
}
setStatus(info.getStatus());
if( info.getViewTableName() != null ) {
setViewTableName(info.getViewTableName());
} else {
setViewTableName(StringConstants.EMPTY_STRING);
}
if( info.getViewProcedureName() != null ) {
setViewProcedureName(info.getViewProcedureName());
} else {
setViewProcedureName(StringConstants.EMPTY_STRING);
}
validate();
}
private void initialize() {
setStatus(Status.OK_STATUS);
this.cachedFirstLines = new String[0];
this.columnInfoList = new ArrayList<TeiidXmlColumnInfo>();
this.namespaceMap = new HashMap<String, String>();
this.parsingStatus = Status.OK_STATUS;
parseXmlFile();
setViewTableName("new_table"); //$NON-NLS-1$
validate();
}
public void setIsUrl(boolean value) {
this.isUrl = value;
}
@Override
public boolean isUrl() {
return this.isUrl;
}
public void setXmlFileUrl(String theUrlValue) {
this.xmlFileUrl = theUrlValue;
}
@Override
public String getXmlFileUrl() {
return this.xmlFileUrl;
}
/**
*
* @return rootPath the root path xquery expression
*/
@Override
public String getRootPath() {
return this.rootPath;
}
/**
*
* @param rootPath
*/
public void setRootPath(String path) {
this.rootPath = path;
// Need to walk through the ColumnInfo objects and have them re-set their paths
for( TeiidXmlColumnInfo colInfo : columnInfoList ) {
colInfo.setRootPath(this.rootPath);
}
validate();
}
private void loadHeader() {
this.cachedFirstLines = new String[0];
Collection<String> lines = new ArrayList<String>(7);
if(this.getDataFile() != null && this.getDataFile().exists()){
FileReader fr=null;
BufferedReader in=null;
try{
int iLines = 0;
fr=new FileReader(this.getDataFile());
in = new BufferedReader(fr);
String str;
while ((str = in.readLine()) != null) {
iLines++;
if( iLines <= numberOfCachedLines ) {
lines.add(str);
}
}
this.numberOfLinesInFile = iLines;
this.cachedFirstLines = lines.toArray(new String[0]);
}catch(Exception e){
Util.log(IStatus.ERROR, e,
Util.getString(I18N_PREFIX + "problemLoadingFileContentsMessage", this.getDataFile().getName())); //$NON-NLS-1$
}
finally{
try{
fr.close();
}catch(java.io.IOException e){}
try{
in.close();
}catch(java.io.IOException e){}
}
}
}
/**
*
* @return cachedFirstLines the <code>String[]</code> array from the data file
*/
@Override
public String[] getCachedFirstLines() {
return this.cachedFirstLines;
}
/**
*
* @return columnInfoList the <code>TeiidXmlColumnInfo[]</code> array parsed from the header in the data file
*/
@Override
public List<TeiidXmlColumnInfo> getColumnInfoList() {
return this.columnInfoList;
}
public void addNewColumn(Object obj) {
if( obj instanceof XmlElement ) {
TeiidXmlColumnInfo newColumnInfo = new TeiidXmlColumnInfo((XmlElement)obj, getRootPath());
this.columnInfoList.add(newColumnInfo);
} else if( obj instanceof XmlAttribute ) {
TeiidXmlColumnInfo newColumnInfo = new TeiidXmlColumnInfo((XmlAttribute)obj, getRootPath());
this.columnInfoList.add(newColumnInfo);
}
validate();
}
public void removeColumn(ITeiidXmlColumnInfo theInfo) {
this.columnInfoList.remove(theInfo);
validate();
}
public void columnChanged(ITeiidXmlColumnInfo columnInfo) {
validate();
}
@Override
public void validate() {
// Validate XQuery Root Path Expression
if( this.getRootPath() == null || this.getRootPath().length() == 0 ) {
setStatus(new Status(IStatus.ERROR, PLUGIN_ID, getString("status.rootPathUndefined"))); //$NON-NLS-1$
return;
}
// must have one or more columns defined
if( this.columnInfoList.isEmpty() ) {
setStatus(new Status(IStatus.ERROR, PLUGIN_ID, getString("status.noColumnsDefined"))); //$NON-NLS-1$
return;
}
// Validate Column names
// Check for ERRORS FIRST
for( ITeiidXmlColumnInfo info : this.getColumnInfoList()) {
if( info.getStatus().getSeverity() == IStatus.ERROR ) {
this.setStatus(info.getStatus());
return;
}
}
for( ITeiidXmlColumnInfo info : this.getColumnInfoList()) {
if( info.getStatus().getSeverity() != IStatus.OK ) {
this.setStatus(info.getStatus());
return;
}
}
// Check for duplicate column names
// Walk through list of columns and cache the names
Collection<String> toUpperNames = new ArrayList<String>(this.getColumnInfoList().size());
for( ITeiidXmlColumnInfo info : this.getColumnInfoList()) {
if( toUpperNames.contains(info.getName().toUpperCase()) ) {
setStatus(new Status(IStatus.ERROR, PLUGIN_ID, getString("status.duplicateColumnNames", info.getName()))); //$NON-NLS-1$
return;
}
toUpperNames.add(info.getName().toUpperCase());
}
// Validate Paths
setStatus(Status.OK_STATUS);
}
/**
*
* @param doProcess the boolean indicator that the user wishes to create view table from this object
*/
public void setDoProcess(boolean doProcess) {
this.doProcess = doProcess;
}
/**
*
* @return doProcess the boolean indicator that the user wishes to create view table from this object
*/
@Override
public boolean doProcess() {
return this.doProcess;
}
/**
*
* @param nLines the number of cached lines from data file
*/
public void setNumberOfCachedFileLines(int nLines) {
this.numberOfCachedLines = nLines;
loadHeader();
}
/**
*
* @return numberOfCachedLines the number of cached lines from data file
*/
@Override
public int getNumberOfCachedFileLines() {
return this.numberOfCachedLines;
}
/**
*
* @return numberOfCachedLines the total number of lines from data file
*/
@Override
public int getNumberOfLinesInFile() {
return this.numberOfLinesInFile;
}
public void setOrdinality(TeiidXmlColumnInfo columnInfo, boolean value) {
// Need to synchronize the setting of this value for a column info.
// Basically only ONE Column can be set to TRUE .... AND ... the datatype MUST be an INTEGER
if( value == false ) {
// Only need to set the columnInfo value
columnInfo.setOrdinality(false);
} else {
for( TeiidXmlColumnInfo info : this.columnInfoList) {
if( !(info == columnInfo) ) {
if( info.getOrdinality() ) {
info.setOrdinality(false);
}
}
}
if( ! columnInfo.getDatatype().equalsIgnoreCase(ITeiidXmlColumnInfo.INTEGER_DATATYPE) ) {
columnInfo.setDatatype(ITeiidXmlColumnInfo.INTEGER_DATATYPE);
}
columnInfo.setOrdinality(true);
}
validate();
}
public void moveColumnUp(TeiidXmlColumnInfo columnInfo) {
int startIndex = getColumnIndex(columnInfo);
//
if( startIndex > 0 ) {
// Make Copy of List & get columnInfo of startIndex-1
TeiidXmlColumnInfo priorInfo = getColumnInfoList().get(startIndex-1);
TeiidXmlColumnInfo[] infos = getColumnInfoList().toArray(new TeiidXmlColumnInfo[0]);
infos[startIndex-1] = columnInfo;
infos[startIndex] = priorInfo;
List<TeiidXmlColumnInfo> colInfos = new ArrayList<TeiidXmlColumnInfo>(infos.length);
for( TeiidXmlColumnInfo info : infos) {
colInfos.add(info);
}
this.columnInfoList = colInfos;
}
}
public void moveColumnDown(TeiidXmlColumnInfo columnInfo) {
int startIndex = getColumnIndex(columnInfo);
if( startIndex < (getColumnInfoList().size()-1) ) {
// Make Copy of List & get columnInfo of startIndex-1
TeiidXmlColumnInfo afterInfo = getColumnInfoList().get(startIndex+1);
TeiidXmlColumnInfo[] infos = getColumnInfoList().toArray(new TeiidXmlColumnInfo[0]);
infos[startIndex+1] = columnInfo;
infos[startIndex] = afterInfo;
List<TeiidXmlColumnInfo> colInfos = new ArrayList<TeiidXmlColumnInfo>(infos.length);
for( TeiidXmlColumnInfo info : infos) {
colInfos.add(info);
}
this.columnInfoList = colInfos;
}
}
public boolean canMoveUp(ITeiidXmlColumnInfo columnInfo) {
return getColumnIndex(columnInfo) > 0;
}
public boolean canMoveDown(ITeiidXmlColumnInfo columnInfo) {
return getColumnIndex(columnInfo) < getColumnInfoList().size()-1;
}
private int getColumnIndex(ITeiidXmlColumnInfo columnInfo) {
int i=0;
for( ITeiidXmlColumnInfo colInfo : getColumnInfoList() ) {
if( colInfo == columnInfo) {
return i;
}
i++;
}
// Shouldn't ever get here!
return -1;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder text = new StringBuilder();
text.append("Teiid Metadata File Info: "); //$NON-NLS-1$
text.append("file name = ").append(getDataFile().getName()); //$NON-NLS-1$
text.append(", view table name = ").append(getViewTableName()); //$NON-NLS-1$
return text.toString();
}
/**
* Returns the current generated SQL string based on an unknown relational model name
* @return the generated SQL string
*/
@Override
public String getSqlStringTemplate() {
return getSqlString("myRelModel"); //$NON-NLS-1$
}
/**
* Returns the current generated SQL string based on an unknown relational model name
* @return the generated SQL string based on the values stored on this instance
*/
@Override
public String getSqlString(String relationalModelName) {
IQueryService queryService = ModelerCore.getTeiidQueryService();
IProcedureService procedureService = queryService.getProcedureService();
return procedureService.getSQLStatement(this, relationalModelName);
}
/**
* Returns the current generated SQL string based on an unknown relational model name
* @param relationalModelName
* @param relationalViewModelName
* @param virtualProcedureName
* @return the generated SQL string based on the values stored on this instance
* @since 8.6
*/
public String getSqlString(String relationalModelName, String relationalViewModelName, String virtualProcedureName) {
IQueryService queryService = ModelerCore.getTeiidQueryService();
IProcedureService procedureService = queryService.getProcedureService();
return procedureService.getSQLStatement(this, relationalModelName, relationalViewModelName, virtualProcedureName);
}
public XmlElement getRootNode() {
return this.rootNode;
}
@Override
public String getCommonRootPath() {
return this.commonRootPath;
}
@Override
public IStatus getParsingStatus() {
return this.parsingStatus;
}
@Override
public String getNamespaceString() {
//
// EXAMPLE: XMLNAMESPACES('http://www.kaptest.com/schema/1.0/party' AS pty)
//
if( this.namespaceMap.isEmpty() ) {
return null;
}
StringBuffer sb = new StringBuffer();
sb.append(XMLNAMESPACES).append(L_PAREN);
int i=0;
for( String prefix : this.namespaceMap.keySet() ) {
// Check for prefix identified by value of 0 length string on first key
if( i == 0 && prefix.length() == 0 ) {
String uri = this.namespaceMap.get(prefix);
sb.append(DEFAULT).append(SPACE).append(S_QUOTE).append(uri).append(S_QUOTE);
} else {
if( prefix.equalsIgnoreCase(XSI_NAMESPACE_PREFIX)) {
continue;
}
if( i > 0 ) {
sb.append(COMMA).append(SPACE);
}
String uri = this.namespaceMap.get(prefix);
sb.append(S_QUOTE).append(uri).append(S_QUOTE).append(SPACE).append(AS).append(SPACE).append(prefix);
}
i++;
}
sb.append(R_PAREN).append(SPACE).append(COMMA).append(SPACE);
return sb.toString();
}
private void parseXmlFile() {
String fileString = getFileAsString(getDataFile());
if( parsingStatus.getSeverity() == IStatus.ERROR ) return;
if( StringUtilities.isEmpty(fileString)) {
String message = Util.getString("TeiidXmlFileInfo.errorXmlFileIsEmpty", getDataFile().getName()); //$NON-NLS-1$
parsingStatus = new Status(IStatus.ERROR, UiConstants.PLUGIN_ID, message);
return;
}
XmlParser xmlParser = new XmlParser();
XmlFileContentHandler contentHandler = new XmlFileContentHandler();
contentHandler.setDocumentLocator(new LocatorImpl());
xmlParser.setContentHandler(contentHandler);
try {
xmlParser.doParse(fileString);
} catch (RuntimeException ex) {
String message = Util.getString("TeiidXmlFileInfo.parsingError", ex.getMessage()); //$NON-NLS-1$
parsingStatus = new Status(IStatus.ERROR, UiConstants.PLUGIN_ID, message, ex);
return;
} catch (IOException ex) {
String message = Util.getString("TeiidXmlFileInfo.parsingError", ex.getMessage()); //$NON-NLS-1$
parsingStatus = new Status(IStatus.ERROR, UiConstants.PLUGIN_ID, message, ex);
return;
} catch (SAXException ex) {
String message = Util.getString("TeiidXmlFileInfo.parsingError", ex.getMessage()); //$NON-NLS-1$
parsingStatus = new Status(IStatus.ERROR, UiConstants.PLUGIN_ID, message, ex);
return;
}
this.namespaceMap.clear();
this.namespaceMap.putAll(contentHandler.getNamespaceMap());
rootNode = contentHandler.getRootElement();
if( rootNode == null ) {
String message = getString("noRootNodeParsingError"); //$NON-NLS-1$
parsingStatus = new Status(IStatus.ERROR, UiConstants.PLUGIN_ID, message);
return;
}
determineCommonRootPath();
setRootPath(this.commonRootPath);
parsingStatus = Status.OK_STATUS;
}
private void determineCommonRootPath(){
StringBuilder commonRoot = new StringBuilder();
List<String> segmentList = new ArrayList<String>();
segmentList.add(rootNode.getFullPath());
for( Object node : rootNode.getChildrenDTDElements() ) {
addChildPaths((XmlElement)node, segmentList);
}
//We parse paths to get all segments. We need to find the shortest
//path up front, since we cannot have a common root greater than
//the shortest path.
String[][] segments = new String[segmentList.size()][];
int shortestPathLength = 0;
for(int i = 0; i < segmentList.size(); i++){
segments[i] = segmentList.get(i).split("/"); //$NON-NLS-1$
if (i==0) shortestPathLength = segments[i].length;
if (shortestPathLength>segments[i].length){
shortestPathLength = segments[i].length;
}
}
for(int j = 0; j < shortestPathLength; j++){
String thisSegment = segments[0][j];
boolean allMatched = true;
for(int i = 0; i < segments.length && allMatched; i++){
if(segments[i].length < j){
allMatched = false;
break;
}
allMatched &= segments[i][j].equals(thisSegment);
}
if(allMatched){
commonRoot.append("/").append(thisSegment) ; //$NON-NLS-1$
}else{
break;
}
}
//Change any double slashes to single slashes
commonRoot = new StringBuilder(commonRoot.toString().replaceAll("//", "/")); //$NON-NLS-1$ //$NON-NLS-2$
commonRootPath = commonRoot.toString();
}
private void addChildPaths(XmlElement element, List<String> segmentList ) {
segmentList.add(element.getFullPath());
for( Object node : element.getChildrenDTDElements() ) {
addChildPaths((XmlElement)node, segmentList);
}
}
/* (non-Javadoc)
* @see org.teiid.designer.query.proc.ITeiidXmlFileInfo#getParameterMap()
*/
@Override
public Map<String, Object> getParameterMap() {
return this.parameterMap;
}
/**
* @param parameterMap
*/
public void setParameterMap(Map<String, Object> parameterMap) {
if( parameterMap == null ) {
parameterMap = Collections.emptyMap();
} else {
this.parameterMap=parameterMap;
}
}
private String getFileAsString(File file) {
FileInputStream fileInputStream = null;
BOMInputStream inputStream = null;
String fileText = null;
try {
int ch;
StringBuffer strContent = new StringBuffer("");
fileInputStream = new FileInputStream(file);
// there may be a BOM (byte order mark) so exclude them all
inputStream = new BOMInputStream(
fileInputStream, UTF_8, UTF_16BE, UTF_16LE, UTF_32BE, UTF_32LE);
while ((ch = inputStream.read()) != -1) {
strContent.append((char) ch);
}
fileText = strContent.toString();
} catch (FileNotFoundException e) {
String message = Util.getString("TeiidXmlFileInfo.couldNotFindXmlFile", getDataFile().getName()); //$NON-NLS-1$
parsingStatus = new Status(IStatus.ERROR, UiConstants.PLUGIN_ID, message, e);
} catch (IOException e) {
String message = Util.getString("TeiidXmlFileInfo.errorReadingXmlFile", getDataFile().getName()); //$NON-NLS-1$
parsingStatus = new Status(IStatus.ERROR, UiConstants.PLUGIN_ID, message, e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
String message = Util.getString("TeiidXmlFileInfo.errorReadingXmlFile", getDataFile().getName()); //$NON-NLS-1$
parsingStatus = new Status(IStatus.WARNING, UiConstants.PLUGIN_ID, message, e);
}
}
}
return fileText;
}
}