package com.openMap1.mapper.query; import org.eclipse.emf.common.util.URI; import org.eclipse.core.resources.IProject; import com.openMap1.mapper.reader.ReaderFactory; import com.openMap1.mapper.reader.XOReader; import com.openMap1.mapper.structures.MapperWrapper; import com.openMap1.mapper.userConverters.DBConnect; import com.openMap1.mapper.util.SystemMessageChannel; import com.openMap1.mapper.util.FileUtil; import com.openMap1.mapper.util.EclipseFileUtil; import com.openMap1.mapper.converters.CSV_Wrapper; import com.openMap1.mapper.converters.V2Converter; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.MappedStructure; import com.openMap1.mapper.Namespace; import com.openMap1.mapper.StructureType; import org.eclipse.core.resources.IFile; import java.sql.Connection; import java.util.Iterator; import java.util.Vector; import java.util.Hashtable; import java.util.StringTokenizer; import java.awt.Color; import org.w3c.dom.Element; /** * The data source for a query or translation test consists * of a mapping set, an instance mapped by the mapping set (or a relational database), and * the class model to which it is mapped. * * Each data source is a row of the Data Source View, and may be active or not. * * @author robert * */ public class DataSource implements MatchSource{ private XOReader reader; // XML reader using MDL public XOReader getReader() throws MapperException { if (reader == null) throw new MapperException("Data source has no reader defined"); return reader; } private Element rootNode; // root node of the DOM public Element getRootNode() {return rootNode;} public void setRootNode(Element el) {rootNode = el;} private URI mappingSetURI; public String mappingSetURIString() {return mappingSetURI.toString();} /** * @return true if the data source is currently active, i.e has * been checked in the data source view , and so may be used in queries and translations * BEWARE - not immediately updated when the box is checked - only later on * getActiveDataSources - i.e the flag here is not much use yet. */ public boolean isActive() {return isActive;} private boolean isActive; /** * tell the data source if it is active or not * Note that this is not called immediately on a checking the box event ,as it should be * @param act */ public void setIsActive(boolean act) {isActive = act;} /** * @return true if the data source is selected in the data source view */ public boolean isSelected() {return isSelected;} private boolean isSelected = false; /** * tell the data source if it is selected or not * @param selected */ public void setIsSelected(boolean selected) {isSelected = selected;} public String mappingSetName() { String sn = ""; StringTokenizer st = new StringTokenizer(mappingSetURIString(),"/\""); while (st.hasMoreTokens()) sn = st.nextToken(); return sn; } /** * @return the mapping set file name without extension */ public String mappingSetNameRoot() { StringTokenizer st = new StringTokenizer(mappingSetName(),"."); return st.nextToken(); } private String sourceType = null; public String sourceType() {return sourceType;} public String instanceFileName() { String sn = ""; StringTokenizer st = new StringTokenizer(instanceURIString,"/\""); while (st.hasMoreTokens()) sn = st.nextToken(); return sn; } /** codes for data sources are now allocated in * sequence 'A', 'B' etc, depending only on the position in the data source view. */ public String getCode() {return shortCode;} public void setCode(String name) {shortCode = name;} private String shortCode = "A"; /** * @return short name of the data source, for us in the matching web service */ public String getShortName() {return shortName;} private String shortName = ""; public void setShortName(String shortName) {this.shortName = shortName;} private String instanceURIString = ""; public String instanceURIString() { return instanceURIString; } /** * @return the IFile containing the instance; will need * to be extended for relational sources with a query defined */ public IFile getInstanceFile() {return EclipseFileUtil.getFile(instanceURIString);} private String classModelURIString = ""; public String classModelURIString() {return classModelURIString;} /** * @return the project containing the class model of this data source */ public IProject getProject() {return FileUtil.getProject(classModelURIString);} public String classModelName() { String sn = ""; StringTokenizer st = new StringTokenizer(classModelURIString,"/\""); while (st.hasMoreTokens()) sn = st.nextToken(); return sn; } /** * * @return the file extension ('.xml' or '.txt') for the file type of this * data source outside any wrapper transformations * @throws MapperException */ public String getExtension() throws MapperException { return (getReader().ms().getExtensions()[0]).substring(1); } private MappedStructure mappedStructure; /** * @return the mapped structure created when the data source was created, * or at the last call of refresh() * @throws MapperException */ public MappedStructure getMappedStructure() throws MapperException { if (mappedStructure != null) return mappedStructure; return getFreshMappedStructure(); } /** * * @return the MappedStructure of this data source, refreshed in case the mappings have * been changed since the data source was created */ public MappedStructure getFreshMappedStructure() throws MapperException { String location = FileUtil.absoluteLocation(mappingSetURIString()); try { mappedStructure = FileUtil.getMappedStructure(location); } catch (Exception ex) {throw new MapperException("Cannot open mapping set: " + ex.getMessage());} return mappedStructure; } // for relational data sources public Connection con() {return con;} private Connection con = null; public void setConnection(Connection con) {this.con = con;} public void connect(String userName, String password) throws MapperException { if (!isRelational()) throw new MapperException("Data Source " + getCode() + " is not Relational"); DBConnect connector = new DBConnect(instanceURIString,userName,password,null); try { connector.connect(); con = connector.con(); } catch (Exception ex) {throw new MapperException("Failed to open relational database for data source " + getCode() + ": " + ex.getMessage());} } public boolean isConnected() {return (con != null);} /** * @return true if this is a relational data source */ public boolean isRelational() { boolean relational = false; try {relational = getMappedStructure().getStructureType() == StructureType.RDBMS;} catch (Exception ex) {relational = false;} return relational; } private String sqlText = ""; public String getSqlText() {return sqlText;} public void setSQLText(String text) {sqlText = text;} /** * @return true if this data source uses the FHIR mapping class */ public boolean isFHIRSource() { boolean isFHIR = false; try { if ((getMappedStructure() != null) && (getMappedStructure().getMappingParameters() != null) && ("com.openMap1.mapper.fhir.FHIRMapper".equals(getMappedStructure().getMappingParameters().getMappingClass()))) isFHIR = true; } catch (Exception ex) {} return isFHIR; } /** * @return true if this data source connects to a FHIR server */ public boolean isNetworkedFHIRSource() { return ((isFHIRSource()) && (instanceURIString != null) && (instanceURIString.startsWith("http"))); } /** * @return the FHIR query which this data source evaluates against its FHIR server */ public String fhirSearch() {return fhirSearch;} private String fhirSearch = null; public void setFhirSearch(String search) {fhirSearch = search;} //----------------------------------------------------------------------------------------- // constructors and refresh //----------------------------------------------------------------------------------------- /** * Constructor when making this data source for the first time - various checks have been * made in DataSourceView * @param reader * @param mappingSetURI * @param rootNode * @param sourceType * @param instanceURI */ public DataSource(XOReader reader, URI mappingSetURI, Element rootNode, String sourceType, String instanceURIString) { this.reader = reader; this.mappingSetURI = mappingSetURI; this.rootNode = rootNode; this.sourceType = sourceType; this.instanceURIString = instanceURIString; classModelURIString = reader.ms().getUMLModelURL(); } /** * constructor when reconstructing a Data Source Set from an inter-session memento or * a saved view file * @param mappingSetURI * @param sourceType * @param instanceURI */ public DataSource(URI mappingSetURI, String sourceType, String instanceURIString) throws MapperException { this.mappingSetURI = mappingSetURI; this.instanceURIString = instanceURIString; this.sourceType = sourceType; /* try to refresh the data source so as to find the class model uri at least; but if * you cannot find the xml instance or connect to the database, leave that to be sovled when * the data source is used in a query, match or translation */ try{refresh();} catch (Exception ex) {} } /** * constructor from an XML element in a match file. * @param sourceEl * @throws MapperException */ public DataSource(Element sourceEl, String rootPath, boolean inEclipse) throws MapperException { sourceType = sourceEl.getAttribute("sourceType"); setCode(sourceEl.getAttribute("code")); setShortName(sourceEl.getAttribute("shortName")); if (inEclipse) { mappingSetURI = URI.createURI(sourceEl.getAttribute("mappingSet")); instanceURIString = sourceEl.getAttribute("instance"); /* try to refresh the data source so as to find the class model uri at least; but if * you cannot find the xml instance or connect to the database, leave that to be solved when * the data source is used in a query, match or translation */ try{refresh();} catch (Exception ex) {} } else { String mappingPath = FileUtil.absoluteLocation(rootPath, sourceEl.getAttribute("mappingSet")); String instancePath = FileUtil.absoluteLocation(rootPath, sourceEl.getAttribute("instance")); String classModelPath = FileUtil.absoluteLocation(rootPath, sourceEl.getAttribute("classModel")); reader = ReaderFactory.makeReader(mappingPath, classModelPath, instancePath); } } /** * constructs a very thin data source, which only contains query results, * consisting of only one record extracted from another data source of query results * @param fullDataSource * @param recNo * @throws MapperException */ public DataSource(MatchSource fullDataSource, int recNo) throws MapperException { if ((recNo < 0)|(recNo > fullDataSource.resultSize())) throw new MapperException("Record number " + recNo + " is out of range when creating a one-record dsata source for matching"); // same code as its parent data source setCode(fullDataSource.getCode()); // result vector is initially empty; add one row to it result.add(fullDataSource.getRow(recNo)); alreadyMatched.add(new Boolean(false)); // set it to have the same column headers setColumnHeaders(fullDataSource.getColumnHeaders()); } /** * refresh this data source for a new query or translation test, * in case the mappings or the XML instance have been altered since * the data source was made */ public void refresh() throws MapperException { // refresh the mapped structure, in case the mappings have been changed mappedStructure = getFreshMappedStructure(); classModelURIString = mappedStructure.getUMLModelURL(); if (isNetworkedFHIRSource()) { if (fhirSearch == null) throw new MapperException("No FHIR search defined for data source " + getCode()); // get the FHIR XML from the search, or throw an exception FHIRClient client = new FHIRClient(instanceURIString()); rootNode = client.getFHIRResponse(fhirSearch); } // refresh the root node of an XML Instance source, in case it has changed else if ((mappedStructure.getStructureType() == StructureType.XSD)| (mappedStructure.getStructureType() == StructureType.V2)) try { rootNode = mappedStructure.getXMLRoot(instanceURIString); if (rootNode == null) throw new MapperException("Cannot open XML instance at " + instanceURIString); } catch(Exception ex) {ex.printStackTrace(); throw new MapperException("Terminated; unknown fault opening XML source");} // there is no refresh for a relational data source; connecting to the source is done outside this class reader = mappedStructure.getXOReader(rootNode, null, new SystemMessageChannel()); } /** * used to step through a large csv data source (called in QueryExecutor) * @throws MapperException */ public void renewDOM() throws MapperException { /* each time this is called, it gets the same mapped structure instance, * with the same csv wrapper class instance, and uses the csv wrapper class * to step through a limited number of lines in the csv file, and make them into a DOM. */ rootNode = getMappedStructure().getXMLRoot(instanceURIString); if (rootNode == null) throw new MapperException("Cannot open XML instance at " + instanceURIString); reader.setRoot(rootNode); } /** * make a specific test database behave as if it needs a specific user name and password; * (which it does not actually, being mySQL seen through odbc). * For that database url, throw an exception if the user name and password are not correct * @param dbURL the test database * @param userName * @param password * @throws MapperException */ private void testPassword(String dbURL, String userName, String password) throws MapperException { String TEST_URL="jdbc:odbc:patientDB"; String TEST_USERNAME="root"; String TEST_PASSWORD="kfr746"; // message("Testing credentials for database '" + dbURL + "'"); if (TEST_URL.equals(dbURL)) { if (!TEST_USERNAME.equals(userName)) throw new MapperException("Invalid test user name '" + userName + "'"); if (!TEST_PASSWORD.equals(password)) throw new MapperException("Invalid test password '" + password + "'"); } } //----------------------------------------------------------------------------------------- // query-related and match-related functionality //----------------------------------------------------------------------------------------- // results from the queryExecutor; used by the matcher private Vector<Vector<CellContent>> result = new Vector<Vector<CellContent>>(); public Vector<Vector<CellContent>> result() {return result;} public int resultSize() {return result.size();} public Vector<CellContent> getRow(int row) {return (result.get(row));} private Vector<Integer> resultCount = null; // records matched in previous matches. index as for result vector private Vector<Boolean> alreadyMatched = new Vector<Boolean>(); public Vector<Boolean> alreadyMatched() {return alreadyMatched;} public void setMatched(int i, boolean isMatched) { alreadyMatched.remove(i); alreadyMatched.insertElementAt(new Boolean(isMatched), i); } public boolean isMatched(int i) {return alreadyMatched.get(i).booleanValue();} public void setAllUnMatched() { alreadyMatched = new Vector<Boolean>(); for (int i = 0; i < result().size(); i++) alreadyMatched.add(new Boolean(false)); } /* Each 'row' is a Vector of cellContents, in the right order for columns. The key is a concatenated string of upper case cell contents, for fast matching with results from other sources. */ private Hashtable<String, Vector<CellContent>> keyedResults = new Hashtable<String, Vector<CellContent>>(); public Hashtable<String, Vector<CellContent>> keyedResults() {return keyedResults;} private Hashtable<String,Integer> keyedResultCounts = new Hashtable<String,Integer>(); public Integer getResultCount(String key) {return keyedResultCounts.get(key);} /* count the rows in the column sorted results, before they are reduced by collating with other sources. */ private int originalRowCount = 0; public void setOriginalRowCount() {originalRowCount = keyedResults.size();} public int getOriginalRowCount() {return originalRowCount;} private boolean hasResult = false; public boolean hasResult() {return hasResult;} private Vector<String[]> columnHeaders; public Vector<String[]> getColumnHeaders() {return columnHeaders;} public void setColumnHeaders(Vector<String[]> columnHeaders) {this.columnHeaders = columnHeaders;}; // re-initialise the raw results and the column sorted results public void unsetResult() { result = new Vector<Vector<CellContent>>(); resultCount = null; alreadyMatched = new Vector<Boolean>(); keyedResults = new Hashtable<String, Vector<CellContent>>(); hasResult = false; } // set up the displayable results public void setResult(Vector<Vector<String[]>> res, Vector<Integer> resCount) { result = new Vector<Vector<CellContent>>(); for (int i = 0; i < res.size(); i++) result.add(makeDisplayRow(res.get(i))); resultCount = null; if (resCount != null) resultCount = resCount; // when a result first comes from a query, none of the records have been matched for (int i = 0; i < res.size(); i++) alreadyMatched.add(new Boolean(false)); setKeyedResults(); hasResult = true; } /** * @return number of rows in this data source that have not been matched. */ public int countUnMatched() { int unMatched = 0; for (int i = 0; i < alreadyMatched.size(); i++) if (!alreadyMatched.get(i).booleanValue()) unMatched++; return unMatched; } /* From a Vector of results, where each row is a Vector of String arrays in correct column order, produce a Hashtable of results, where each row is a Vector of cellContents. The key is a concatenated string of upper case cell contents. */ private void setKeyedResults() { keyedResults = new Hashtable<String, Vector<CellContent>>(); keyedResultCounts = new Hashtable<String,Integer>(); for (int i = 0; i < result.size(); i++) { Vector<CellContent> displayRow = result.get(i); String key = rowKey(displayRow); keyedResults.put(key,displayRow); if (resultCount != null) keyedResultCounts.put(key, resultCount.get(i)); } } /** * * @param row row returned from the QueryExecutor * @return displayable row of CellContent objects. */ public Vector<CellContent> makeDisplayRow(Vector<String[]> row) { Vector<CellContent> displayRow = new Vector<CellContent>(); for (int j = 0; j < row.size(); j++) displayRow.add(new CellContent(row.get(j)[2],Color.black)); return displayRow; } /** * * @return column headers - currently unqualified class name, followed by property name; * which is unsatisfactory for small data type classes */ public Vector<CellContent> getHeaderRow() { Vector<CellContent> colSortedRow = new Vector<CellContent>(); for (int j = 0; j < columnHeaders.size(); j++) { String[] f = columnHeaders.get(j); String header = f[0] + "." + f[1]; colSortedRow.addElement(new CellContent(header,Color.black)); } return colSortedRow; } /* a string key for each row, to match on all columns in the row Key is converted to upper case, so case is ignored in row matching. */ private String rowKey(Vector<CellContent> row) { String key = ""; for (int i = 0; i < row.size(); i++) { CellContent cc = row.elementAt(i); key = key + "£$" + cc.getText().toUpperCase(); } return key; } //----------------------------------------------------------------------------------------- // used for segmenting very large RDBMS and CSV queries and matches //----------------------------------------------------------------------------------------- public boolean isCSVSource() { boolean isCSV = false; try { MapperWrapper wrap = getMappedStructure().getWrapper(); isCSV = ((wrap != null) && (wrap instanceof CSV_Wrapper)); } catch (Exception ex) {} return isCSV; } //----------------------------------------------------------------------------------------- // odds and ends //----------------------------------------------------------------------------------------- /** * @return true if this is a data source for a V2.XML message */ public boolean isV2DataSource() { boolean isV2 = false; if (reader.ms().getMappingParameters() != null) for(Iterator<Namespace> it = reader.ms().getMappingParameters().getNameSpaces().iterator();it.hasNext();) if (it.next().getURL().equals(V2Converter.V2_NAMESPACE_URI)) isV2 = true; return isV2; } }