/*
* The MIT License (MIT)
*
* Copyright (c) 2007-2015 Broad Institute
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.broad.igv.dev.db;
import org.apache.log4j.Logger;
import org.broad.igv.session.SubtlyImportant;
import org.broad.igv.util.ParsingUtils;
import org.broad.igv.util.ResourceLocator;
import org.broad.igv.util.Utilities;
import org.w3c.dom.Document;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.*;
/**
* Object representation of the location of a database,
* specifying address, tables, etc.
* User: jacob
* Date: 2013-Jan-14
*/
@XmlRootElement(name = "database")
@XmlAccessorType(XmlAccessType.NONE)
@XmlSeeAlso(DBProfile.DBTable.class)
public class DBProfile {
private static Logger log = Logger.getLogger(DBProfile.class);
@XmlAttribute private String name;
@XmlAttribute private String description;
@XmlAttribute private String version;
@XmlAttribute private String subprotocol;
@XmlAttribute private String host;
@XmlAttribute private String path;
@XmlAttribute private String port;
@XmlAttribute private String username;
@XmlAttribute private String password;
@XmlElement(name = "table")
private List<DBTable> tableList;
public List<DBTable> getTableList() {
return tableList;
}
@XmlTransient
private ResourceLocator dbLocator;
public ResourceLocator getDBLocator() {
if(dbLocator == null){
dbLocator = new ResourceLocator(DBManager.createConnectionURL(subprotocol, host, path, port));
dbLocator.setUsername(username);
dbLocator.setPassword(password);
}
return dbLocator;
}
private static JAXBContext jc = null;
public static JAXBContext getJAXBContext() throws JAXBException {
if(jc == null){
jc = JAXBContext.newInstance(DBProfile.class);
}
return jc;
}
public static DBProfile parseProfile(String profilePath){
InputStream profileStream = null;
try {
profileStream = ParsingUtils.openInputStream(profilePath);
} catch (IOException e) {
try {
if (profileStream != null) profileStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
throw new RuntimeException("Unable to open DB profile", e);
}
Document doc = null;
try{
doc = Utilities.createDOMDocumentFromXmlStream(profileStream);
}catch (Exception e){
throw new RuntimeException("Error parsing DB Profile", e);
}
try{
JAXBContext jc = getJAXBContext();
Unmarshaller u = jc.createUnmarshaller();
// u.setListener(new Unmarshaller.Listener() {
// @Override
// public void afterUnmarshal(Object target, Object parent) {
// if(target instanceof DBTable && parent instanceof DBProfile){
// ((DBTable) target).setDbLocator(((DBProfile) parent).getDBLocator());
// }
// }
// });
return u.unmarshal(doc, DBProfile.class).getValue();
}catch(JAXBException e){
throw new RuntimeException("Error unmarshalling DB Profile, it may be misformed", e);
}
}
@SubtlyImportant
public void afterUnmarshal(Unmarshaller u, Object parent) {
dbLocator = getDBLocator();
for (DBTable table : getTableList()) {
table.setDbLocator(dbLocator);
}
}
public String getHost() {
return host;
}
public String getPassword() {
return password;
}
public String getPort() {
return port;
}
public String getSubprotocol() {
return subprotocol;
}
public String getUsername() {
return username;
}
public String getPath() {
return path;
}
public String getName() {
return name;
}
public void setHost(String host) {
this.host = host;
}
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
this.password = password;
}
public void setPath(String path) {
this.path = path;
}
public void setPort(String port) {
this.port = port;
}
public void setSubprotocol(String subprotocol) {
this.subprotocol = subprotocol;
}
public void setUsername(String username) {
this.username = username;
}
void addTable(DBTable newTable) {
if(tableList == null){
tableList = new ArrayList<DBTable>();
}
tableList.add(newTable);
}
/**
* Checks for {@code name}, {@code host}, {@code path},
* and {@code username}
* See {@link #checkMissingValues(Object, String[]}
* @return
*/
public List<String> checkMissingValues() {
String[] requiredGetters = new String[]{"getName", "getHost", "getPath", "getUsername"};
return checkMissingValues(this, requiredGetters);
}
/**
* Checks {@code object} for non-null values (or empty strings)
* retrieved by getter in {@code requiredGetters}. If the getter is not found or cannot
* be accessed, an error is logged (access restrictions are followed).
* @param object The object to check for missing values
* @param requiredGetters The getter method names to use. Must take no arguments
* @return
*/
public static List<String> checkMissingValues(Object object, String[] requiredGetters){
List<String> missing = new ArrayList<String>(requiredGetters.length);
for(String reqGetter: requiredGetters){
Method method = null;
try {
method = object.getClass().getMethod(reqGetter);
Object value = method.invoke(object);
if(value instanceof String && ((String) value).length() == 0){
value = null;
}
if(value == null) missing.add(reqGetter);
} catch (Exception e) {
log.error(e.getMessage(), e);
missing.add(reqGetter);
}
}
return missing;
}
/**
* Object representation of a single {@code table} element of
* a database profile. Contains static method for parsing dbXML files
* <p/>
* User: jacob
* Date: 2012-Oct-31
*/
@XmlAccessorType(XmlAccessType.NONE)
public static class DBTable {
private static Logger log = Logger.getLogger(DBTable.class);
@XmlAttribute private String name;
@XmlAttribute private String format;
@XmlAttribute private String binColName;
@XmlAttribute private String chromoColName;
@XmlAttribute private String posStartColName;
@XmlAttribute private String posEndColName;
@XmlAttribute private int startColIndex = 1;
@XmlAttribute private int endColIndex = Integer.MAX_VALUE;
@XmlAttribute private String baseQuery;
@XmlElement(name = "column")
private List<Column> columnList;
@XmlElement(name = "header")
private List<String> headerLines;
@XmlTransient
private HashMap<Integer, String> columnLabelMap;
@XmlTransient
private ResourceLocator dbLocator;
/**
* Generally just intended for testing, where all we try
* to do is get all data from a db and don't need anything fancy
*
* @param dbLocator
* @param tableName
*/
public static DBTable build(ResourceLocator dbLocator, String tableName) {
return new DBTable(dbLocator, tableName, null, null, null, null, null, 1, Integer.MAX_VALUE - 1, null, null, null);
}
private DBTable(){}
DBTable(ResourceLocator dbLocator, String name){
this.dbLocator = dbLocator;
this.name = name;
}
public DBTable(ResourceLocator dbLocator, String name, String format, String binColName,
String chromoColName, String posStartColName, String posEndColName, int startColIndex, int endColIndex,
HashMap<Integer, String> columnLabelMap, String baseQuery, List<String> headerLines) {
this.dbLocator = dbLocator;
this.name = name;
this.format = format;
this.binColName = binColName;
this.chromoColName = chromoColName;
this.posStartColName = posStartColName;
this.posEndColName = posEndColName;
this.startColIndex = startColIndex;
this.endColIndex = endColIndex;
this.columnLabelMap = columnLabelMap;
this.baseQuery = baseQuery;
this.headerLines = headerLines;
}
@SubtlyImportant
public void afterUnmarshal(Unmarshaller u, Object parent) {
if(columnList != null){
columnLabelMap = ColumnMapAdapter.unmarshal(columnList);
}
}
public List<String> checkMissingValues(){
String[] requiredGetters = new String[]{"getFormat", "getChromoColName", "getPosStartColName", "getPosEndColName"};
return DBProfile.checkMissingValues(this, requiredGetters);
}
public ResourceLocator getDbLocator() {
return dbLocator;
}
public String getBinColName() {
return binColName;
}
public String getChromoColName() {
return chromoColName;
}
public int getEndColIndex() {
return endColIndex;
}
public String getFormat() {
return format;
}
public String getPosEndColName() {
return posEndColName;
}
public String getPosStartColName() {
return posStartColName;
}
public int getStartColIndex() {
return startColIndex;
}
public String getName() {
return name;
}
public String getBaseQuery() {
return baseQuery;
}
public Map<Integer, String> getColumnLabelMap() {
return columnLabelMap;
}
public List<String> getHeaderLines() {
return headerLines;
}
void setDbLocator(ResourceLocator dbLocator) {
this.dbLocator = dbLocator;
}
public void setBinColName(String binColName) {
this.binColName = binColName;
}
public void setChromoColName(String chromoColName) {
this.chromoColName = chromoColName;
}
public void setEndColIndex(int endColIndex) {
this.endColIndex = endColIndex;
}
public void setFormat(String format) {
this.format = format;
}
public void setPosEndColName(String posEndColName) {
this.posEndColName = posEndColName;
}
public void setPosStartColName(String posStartColName) {
this.posStartColName = posStartColName;
}
public void setStartColIndex(int startColIndex) {
this.startColIndex = startColIndex;
}
/**
* Return an array of column labels in specified ordinal positions
*
* @return
*/
public static String[] columnMapToArray(Map<Integer, String> columnLabelMap) {
List<Integer> arrayIndexes = new ArrayList<Integer>(columnLabelMap.keySet());
Collections.sort(arrayIndexes);
int minArrayIndex = arrayIndexes.get(0);
int maxArrayIndex = arrayIndexes.get(arrayIndexes.size() - 1);
int colCount = maxArrayIndex + 1;
String[] tokens = new String[colCount];
for (int cc = minArrayIndex; cc <= maxArrayIndex; cc++) {
tokens[cc] = columnLabelMap.get(cc);
}
return tokens;
}
private static class ColumnMapAdapter extends XmlAdapter<XmlMap, HashMap<Integer, String>> {
@Override
public HashMap<Integer, String> unmarshal(XmlMap v){
return unmarshal(v.column);
}
public static HashMap<Integer, String> unmarshal(List<Column> columnList){
HashMap<Integer, String> result = new HashMap<Integer, String>(columnList.size());
for(Column entry: columnList){
result.put(entry.fileIndex, entry.colLabel);
}
return result;
}
@Override
public XmlMap marshal(HashMap<Integer, String> v){
XmlMap result = new XmlMap();
for(Map.Entry<Integer, String> entry: v.entrySet()){
Column mapEntry = new Column();
mapEntry.fileIndex = entry.getKey();
mapEntry.colLabel = entry.getValue();
result.column.add(mapEntry);
}
return result;
}
}
private static class XmlMap {
public List<Column> column =
new ArrayList<Column>();
}
private static class Column {
@XmlAttribute private Integer fileIndex;
@XmlAttribute private String colLabel;
}
}
}