/* DatabaseEntrySource.java
*
* created: Mar 2005
*
* This file is part of Artemis
*
* Copyright (C) 2001 Genome Research Limited
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
package uk.ac.sanger.artemis.components.database;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.IOException;
import java.io.Serializable;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import uk.ac.sanger.artemis.Entry;
import uk.ac.sanger.artemis.EntrySource;
import uk.ac.sanger.artemis.Options;
import uk.ac.sanger.artemis.sequence.Bases;
import uk.ac.sanger.artemis.sequence.NoSequenceException;
import uk.ac.sanger.artemis.util.DatabaseDocument;
import uk.ac.sanger.artemis.util.DatabaseLocationParser;
import uk.ac.sanger.artemis.util.InputStreamProgressListener;
import uk.ac.sanger.artemis.util.OutOfRangeException;
import uk.ac.sanger.artemis.util.StringVector;
import uk.ac.sanger.artemis.components.filetree.LocalAndRemoteFileManager;
import uk.ac.sanger.artemis.components.genebuilder.JExtendedComboBox;
import uk.ac.sanger.artemis.io.UI;
import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
import uk.ac.sanger.artemis.io.InvalidKeyException;
import uk.ac.sanger.artemis.io.EntryInformationException;
import uk.ac.sanger.artemis.io.Range;
/**
*
* This is an EntrySource that reads Entry objects from a relational database.
*
*/
public class DatabaseEntrySource implements EntrySource, Serializable
{
/** */
private static final long serialVersionUID = 1L;
private String location;
private JPasswordField pfield;
private boolean splitGFFEntry;
private boolean readOnly = false;
/**
* Create a new DatabaseEntrySource.
*/
public DatabaseEntrySource()
{
}
/**
* @param bases
* The Bases object to pass to the Entry constructor.
* @param show_progress
* If true a InputStreamProgressDialog will be shown while loading.
* (Not implemented)
* @exception OutOfRangeException
* Thrown if one of the features in the Entry is out of range of
* the Bases object.
* @return null if and only if the read is cancelled by the user or if the
* read fails.
*/
public Entry getEntry(final Bases bases, final boolean show_progress)
throws OutOfRangeException, IOException
{
return null;
}
public Entry getEntry(final boolean show_progress)
throws OutOfRangeException, IOException
{
return null;
}
/**
* Get an Entry object from this source.
*
* @param id
* Feature ID to read in.
* @exception OutOfRangeException
* Thrown if one of the features in the Entry is out of range of
* the Bases object.
* @exception NoSequenceException
* Thrown if the entry that we read has no sequence.
* @return null if and only if the user cancels the read or if the read fails.
*/
public Entry getEntry(String id, String schema,
InputStreamProgressListener progress_listener)
throws OutOfRangeException, NoSequenceException, IOException
{
return makeFromID(null, id, schema, progress_listener, null);
}
public Entry getEntry(String id, String schema,
InputStreamProgressListener progress_listener, Range range)
throws OutOfRangeException, NoSequenceException, IOException
{
return makeFromID(null, id, schema, progress_listener, range);
}
/**
* Returns true if and only if this EntrySource always returns "full" entries.
* ie. entries that contain features and sequence.
*/
public boolean isFullEntrySource()
{
return true;
}
/**
* Return the name of this source (for display to the user in menus).
*/
public String getSourceName()
{
return "Database";
}
/**
*
* Set the database location as:
* jdbc:postgresql://host:port/database?user=username
*
*/
public boolean setLocation(final boolean promptUser)
{
ILoginPrompt promptPanel; // = new DatabaseLoginPrompt();
if (UI.mode == UI.UIMode.SWING)
{
promptPanel = new DatabaseLoginPrompt();
}
else
{
promptPanel = new DatabaseLoginPromptConsole();
}
if(promptUser)
{
boolean userEntered = promptPanel.prompt();
if (userEntered == false)
{
return false;
}
//
// int n = JOptionPane.showConfirmDialog(null, promptPanel,
// "Enter Database Address",
// JOptionPane.OK_CANCEL_OPTION,
// JOptionPane.QUESTION_MESSAGE);
// if(n == JOptionPane.CANCEL_OPTION)
// return false;
}
pfield = promptPanel.getPfield();
DatabaseLocationParser dlp = new DatabaseLocationParser();
dlp.setHost(promptPanel.getServer());
dlp.setPort(promptPanel.getPort());
dlp.setDatabase(promptPanel.getDb());
dlp.setUsername(promptPanel.getUser());
dlp.setSSL(promptPanel.getSSL());
location = dlp.getCompleteURL();
return true;
}
public DatabaseDocument getDatabaseDocument()
{
// ArtemisConsole.info(location, "location");
// ArtemisConsole.info(pfield.getText(), "password");
return new DatabaseDocument(location, pfield);
}
/**
* Set whether to split into separate entries.
* @param splitGFFEntry
*/
protected void setSplitGFF(final boolean splitGFFEntry)
{
this.splitGFFEntry = splitGFFEntry;
}
/**
* Given an database feature identifier, this makes an Entry
.
*
* @param bases
* If this is null a new Bases object will be created for the Entry
* once it has been read from the server. If not null then it will be
* passed to the Entry constructor.
* @param id
* The id of the entry in the database
* @param schema
* The schema of the entry in the database
* @exception OutOfRangeException
* Thrown if one of the features in the Entry is out of range of
* the Bases object.
*/
private Entry makeFromID(final Bases bases, final String id,
final String schema,
InputStreamProgressListener progress_listener,
final Range range)
throws OutOfRangeException, IOException
{
try
{
DatabaseDocumentEntry db_entry = null;
DatabaseDocument doc = new DatabaseDocument(location, pfield, id,
schema, splitGFFEntry,
progress_listener);
doc.setLazyFeatureLoad(LocalAndRemoteFileManager.lazyLoad.isSelected());
if(range != null)
doc.setRange(range);
db_entry = new DatabaseDocumentEntry(doc, null);
db_entry.setReadOnly(isReadOnly());
final Bases real_bases;
if(bases == null)
{
if (db_entry.getSequence() == null)
{
UI.error("The selected entry contains no sequence: " + id, "No Sequence");
return null;
}
real_bases = new Bases(db_entry.getSequence());
}
else
real_bases = bases;
return new Entry(real_bases, db_entry);
}
catch(InvalidKeyException e)
{
UI.error("Unexpected error while accessing " + id + ": " + e, "Invalid Key");
}
catch(EntryInformationException e)
{
UI.error("Failed to get entry: " + e,"Entry Information Exception");
}
return null;
}
protected DatabaseDocumentEntry[] makeFromGff(final DatabaseDocument doc,
String id, String schema)
throws OutOfRangeException, IOException
{
DatabaseDocumentEntry[] db_entry = null;
try
{
DatabaseDocument[] new_docs = doc.getGffDocuments(location, id, schema);
db_entry = new DatabaseDocumentEntry[new_docs.length];
for(int i = 0; i < new_docs.length; i++)
{
new_docs[i].setLazyFeatureLoad(doc.isLazyFeatureLoad());
db_entry[i] = new DatabaseDocumentEntry(new_docs[i], null);
}
}
catch (EntryInformationException e)
{
// JOptionPane.showMessageDialog(null,
// "Failed to get entry: " + e,
// "Entry Information Exception",
// JOptionPane.ERROR_MESSAGE);
UI.error("Failed to get entry: " + e,"Entry Information Exception");
}
return db_entry;
}
public String getLocation()
{
return location;
}
public JPasswordField getPfield()
{
return pfield;
}
public boolean isReadOnly()
{
return readOnly;
}
public void setReadOnly(boolean readOnly)
{
this.readOnly = readOnly;
}
}
/*
* A common interface for login prompting, irrespective of the implementation.
*/
interface ILoginPrompt
{
boolean prompt();
JPasswordField getPfield();
String getServer();
String getPort();
String getDb();
String getUser();
boolean getSSL();
}
/**
* Database login panel
*/
class DatabaseLoginPrompt extends JPanel implements ILoginPrompt
{
private static final long serialVersionUID = 1L;
private JPasswordField pfield;
private JTextField server;
private JTextField port;
private JTextField db;
private JTextField user;
private JCheckBox ssl;
public DatabaseLoginPrompt()
{
super(new GridBagLayout());
final GridBagConstraints c = new GridBagConstraints();
// column 1
int nrow = 1;
c.anchor = GridBagConstraints.EAST;
c.gridx = 0;
c.gridy = nrow;
add(new JLabel("Server : "), c);
c.gridy = ++nrow;
add(new JLabel("Port : "), c);
c.gridy = ++nrow;
add(new JLabel("Database : "), c);
c.gridy = ++nrow;
add(new JLabel("User : "), c);
c.gridy = ++nrow;
add(new JLabel("Password : "), c);
c.gridy = ++nrow;
add(new JLabel("SSL : "), c);
// column 2
nrow = 1;
c.anchor = GridBagConstraints.WEST;
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 1;
c.gridy = nrow;
server = new JTextField("localhost");
add(server, c);
c.gridy = ++nrow;
port = new JTextField("5432");
add(port, c);
c.gridy = ++nrow;
db = new JTextField("chado");
add(db, c);
c.gridy = ++nrow;
user = new JTextField("");
add(user, c);
c.gridy = ++nrow;
pfield = new JPasswordField(16);
add(pfield, c);
c.gridy = ++nrow;
ssl = new JCheckBox();
add(ssl, c);
// given -Dchado=localhost:port/dbname?username
if(System.getProperty("chado") != null)
setFromURL(System.getProperty("chado").trim());
if (System.getProperty("chadoPassword") != null)
{
//System.out.println("detected chadoPassword: " + System.getProperty("chadoPassword"));
pfield.setText(System.getProperty("chadoPassword"));
}
c.gridx = 1;
c.gridy = ++nrow;
add(getServerComboBox(), c);
c.gridx = 0;
c.anchor = GridBagConstraints.EAST;
add(new JLabel("Available databases : "), c);
}
public boolean prompt()
{
int n = JOptionPane.showConfirmDialog(null, this,
"Enter Database Address",
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE);
if(n == JOptionPane.CANCEL_OPTION)
return false;
return true;
}
private void setFromURL(String db_url)
{
DatabaseLocationParser dlp = new DatabaseLocationParser(db_url);
server.setText(dlp.getHost());
port.setText("" + dlp.getPort());
db.setText(dlp.getDatabase());
user.setText(dlp.getUsername());
ssl.setSelected(dlp.isSSLEnabled());
}
/**
* Get a combo box of the available database servers.
* @return
*/
private JExtendedComboBox getServerComboBox()
{
// known servers
StringVector serversVector = Options.getOptions().getOptionValues("chado_servers");
if(serversVector == null)
{
serversVector = new StringVector();
serversVector.add("GeneDB (read-only)");
serversVector.add("db.genedb.org:5432/snapshot?genedb_ro");
}
// set the default to the chado property value
if(System.getProperty("chado") != null &&
!serversVector.contains(System.getProperty("chado").trim()))
{
serversVector.add(0, "Default");
serversVector.add(1, System.getProperty("chado").trim());
}
final String servers[][] = new String[2][serversVector.size()/2];
int nserver = 0;
for(int i=0; i