/*******************************************************************************
* Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.mappingsmodel.db;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;
import org.eclipse.persistence.tools.workbench.mappingsmodel.MWModel;
import org.eclipse.persistence.tools.workbench.mappingsmodel.ProblemConstants;
import org.eclipse.persistence.tools.workbench.platformsmodel.DatabasePlatform;
import org.eclipse.persistence.tools.workbench.utility.CollectionTools;
import org.eclipse.persistence.tools.workbench.utility.iterators.ArrayIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.CloneListIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.NullIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.TransformationIterator;
import org.eclipse.persistence.tools.workbench.utility.node.Node;
import org.eclipse.persistence.tools.workbench.utility.string.StringTools;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.internal.security.JCEEncryptor;
import org.eclipse.persistence.mappings.DirectToFieldMapping;
import org.eclipse.persistence.oxm.XMLDescriptor;
import org.eclipse.persistence.oxm.mappings.XMLCompositeDirectCollectionMapping;
import org.eclipse.persistence.oxm.mappings.XMLDirectMapping;
import org.eclipse.persistence.sessions.Connector;
import org.eclipse.persistence.sessions.DatabaseLogin;
import org.eclipse.persistence.sessions.Session;
public final class MWLoginSpec
extends MWModel
{
/** the name should never be null or empty */
private volatile String name;
public static final String NAME_PROPERTY = "name";
private volatile String driverClassName;
public static final String DRIVER_CLASS_NAME_PROPERTY = "driverClassName";
// virtual collection
public static final String CANDIDATE_URLS_COLLECTION = "candidateURLs";
private volatile String url;
public static final String URL_PROPERTY = "url";
private volatile String userName;
public static final String USER_NAME_PROPERTY = "userName";
private volatile String password;
public static final String PASSWORD_PROPERTY = "password";
private volatile boolean savePassword;
public static final String SAVE_PASSWORD_PROPERTY = "savePassword";
private List driverClasspathEntries;
public static final String DRIVER_CLASSPATH_ENTRIES_LIST = "driverClasspathEntries";
/** the JDBC drivers we know about and their URLs */
private static Collection driverSpecs;
/** cache this - it's expensive to instantiate */
private static JCEEncryptor encryptor;
/** preferences */
public static final String DB_DRIVER_CLASS_PREFERENCE = "database driver class";
public static final String DB_CONNECTION_URL_PREFERENCE = "database connection url";
// ********** static methods **********
public static XMLDescriptor buildDescriptor() {
XMLDescriptor descriptor = new XMLDescriptor();
descriptor.setJavaClass(MWLoginSpec.class);
descriptor.addDirectMapping("name", "name/text()");
((XMLDirectMapping) descriptor.addDirectMapping("driverClassName", "driver-class/text()")).setNullValue("");
((XMLDirectMapping) descriptor.addDirectMapping("url", "url/text()")).setNullValue("");
((XMLDirectMapping) descriptor.addDirectMapping("userName", "user-name/text()")).setNullValue("");
descriptor.addDirectMapping("password", "getPasswordForTopLink", "setPasswordForTopLink", "password/text()");
((XMLDirectMapping) descriptor.addDirectMapping("savePassword", "save-password/text()")).setNullValue(Boolean.FALSE);
XMLCompositeDirectCollectionMapping driverClasspathMapping = new XMLCompositeDirectCollectionMapping();
driverClasspathMapping.setAttributeName("driverClasspathEntries");
driverClasspathMapping.setXPath("driver-classpath-entries/entry/text()");
descriptor.addMapping(driverClasspathMapping);
return descriptor;
}
/**
* return the driver class names we know about
*/
public static Iterator commonDriverClassNames() {
return new TransformationIterator(driverSpecs()) {
protected Object transform(Object next) {
return ((DriverSpec) next).getDriverClassName();
}
};
}
public static int commonDriverClassNamesSize() {
return getDriverSpecs().size();
}
/**
* return the URLs typically associated with the specified
* driver class name
*/
private static Iterator urlsForDriverClassNamed(String driverClassName) {
DriverSpec ds = driverSpecFor(driverClassName);
return (ds == null) ? NullIterator.instance() : ds.urls();
}
private static int urlsForDriverClassNamedSize(String driverClassName) {
DriverSpec ds = driverSpecFor(driverClassName);
return (ds == null) ? 0 : ds.urlsSize();
}
private static boolean urlsForDriverClassNamedContains(String driverClassName, String url) {
DriverSpec ds = driverSpecFor(driverClassName);
return (ds == null) ? false : ds.containsURL(url);
}
/**
* return the URL most commonly used with the specified
* driver class name
*/
private static String defaultURLForDriverClassNamed(String driverClassName) {
DriverSpec ds = driverSpecFor(driverClassName);
return (ds == null) ? null : ds.defaultURL();
}
private static DriverSpec driverSpecFor(String driverClassName) {
for (Iterator stream = driverSpecs(); stream.hasNext(); ) {
DriverSpec ds = (DriverSpec) stream.next();
if (ds.getDriverClassName().equals(driverClassName)) {
return ds;
}
}
return null;
}
private static Iterator driverSpecs() {
return getDriverSpecs().iterator();
}
private synchronized static Collection getDriverSpecs() {
if (driverSpecs == null) {
driverSpecs = buildDriverSpecs();
}
return driverSpecs;
}
// TODO move to DatabasePlatform and store in its XML file?
private static Collection buildDriverSpecs() {
Collection specs = new ArrayList(30);
specs.add(new DriverSpec("com.neon.jdbc.Driver", "jdbc:neon:"));
specs.add(new DriverSpec("com.pointbase.jdbc.jdbcUniversalDriver", "jdbc:pointbase:"));
specs.add(new DriverSpec("com.sybase.jdbc3.jdbc.SybDriver", "jdbc:sybase:Tds:"));
specs.add(new DriverSpec("com.sybase.jdbc2.jdbc.SybDriver", "jdbc:sybase:Tds:"));
specs.add(new DriverSpec("com.sybase.jdbc.SybDriver", "jdbc:sybase:Tds:"));
specs.add(new DriverSpec("COM.ibm.db2.jdbc.app.DB2Driver", "jdbc:db2:"));
specs.add(new DriverSpec("COM.ibm.db2.jdbc.net.DB2Driver", "jdbc:db2:"));
specs.add(new DriverSpec("com.ibm.db2.jcc.DB2Driver", "jdbc:db2://"));
specs.add(new DriverSpec("com.mysql.jdbc.Driver", "jdbc:mysql://"));
specs.add(new DriverSpec("borland.jdbc.Bridge.LocalDriver", "jdbc:BorlandBridge:"));
specs.add(new DriverSpec("borland.jdbc.Broker.RemoteDriver", "jdbc:BorlandBridge:"));
specs.add(new DriverSpec("intersolv.jdbc.sequelink.SequeLinkDriver", "jdbc:sequelink:"));
String[] oracleURLs =
new String[] {
"jdbc:oracle:thin:@<HOST>:<PORT>:<SID>",
"jdbc:oracle:oci:@<HOST>:<PORT>:<SID>",
"jdbc:oracle:oci7:@<HOST>:<PORT>:<SID>",
"jdbc:oracle:oci8:@<HOST>:<PORT>:<SID>"
};
specs.add(new DriverSpec("oracle.jdbc.OracleDriver", oracleURLs));
specs.add(new DriverSpec("oracle.jdbc.driver.OracleDriver", oracleURLs));
specs.add(new DriverSpec("com.oracle.ias.jdbc.db2.DB2Driver", "jdbc:oracle:db2://"));
specs.add(new DriverSpec("com.oracle.ias.jdbc.sqlserver.SQLServerDriver", "jdbc:oracle:sqlserver://"));
specs.add(new DriverSpec("com.oracle.ias.jdbc.sybase.SybaseDriver", "jdbc:oracle:sybase://"));
specs.add(new DriverSpec("org.hsqldb.jdbcDriver", "jdbc:hsqldb:"));
specs.add(new DriverSpec("sun.jdbc.odbc.JdbcOdbcDriver", "jdbc:odbc:"));
specs.add(new DriverSpec("weblogic.jdbc.oci.Driver", "jdbc:weblogic:oracle:"));
specs.add(new DriverSpec("weblogic.jdbc.dblib.Driver", new String[] { "jdbc:weblogic:mssqlserver:", "jdbc:weblogic:sybase" }));
specs.add(new DriverSpec("weblogic.jdbc.informix4.Driver", "jdbc:weblogic:informix4:"));
specs.add(new DriverSpec("weblogic.jdbc.jts.Driver", "jdbc:weblogic:jts:"));
specs.add(new DriverSpec("weblogic.jdbc.mssqlserver4.Driver", "jdbc:weblogic:mssqlserver4:"));
specs.add(new DriverSpec("weblogic.jdbc.pool.Driver", "jdbc:weblogic:pool:"));
specs.add(new DriverSpec("weblogic.jdbc.t3client.Driver", "jdbc:weblogic:t3Client:"));
specs.add(new DriverSpec("weblogic.jdbc.t3.Driver", "jdbc:weblogic:t3:"));
specs.add(new DriverSpec("com.timesten.jdbc.TimesTenDriver", "jdbc:timesten:direct:<SID>"));
return specs;
}
private static JCEEncryptor getEncryptor() {
if (encryptor == null) {
try {
encryptor = new JCEEncryptor();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
return encryptor;
}
// ********** constructors **********
/**
* Default constructor - for TopLink use only.
*/
private MWLoginSpec() {
super();
}
MWLoginSpec(MWDatabase database, String name) {
super(database);
this.name = name;
}
// ********** initialization **********
/**
* initialize persistent state
*/
protected void initialize(Node parent) {
super.initialize(parent);
this.savePassword = false;
this.driverClasspathEntries = new Vector();
}
// ********** accessors **********
public String getName() {
return this.name;
}
public void setName(String name) {
Object old = this.name;
this.name = name;
this.firePropertyChanged(NAME_PROPERTY, old, name);
if (this.attributeValueHasChanged(old, name)) {
this.getProject().nodeRenamed(this);
}
}
public String getDriverClassName() {
return this.driverClassName;
}
public void setDriverClassName(String driverClassName) {
String old = this.driverClassName;
this.driverClassName = driverClassName;
this.firePropertyChanged(DRIVER_CLASS_NAME_PROPERTY, old, driverClassName);
if (this.attributeValueHasChanged(old, driverClassName)) {
// only change the URL if it is easy to re-create
if ((this.url == null) || urlsForDriverClassNamedContains(old, this.url)) {
this.setURL(defaultURLForDriverClassNamed(driverClassName));
}
this.fireCollectionChanged(CANDIDATE_URLS_COLLECTION);
}
}
public String getURL() {
return this.url;
}
public void setURL(String url) {
Object old = this.url;
this.url = url;
this.firePropertyChanged(URL_PROPERTY, old, url);
}
public String getUserName() {
return this.userName;
}
public void setUserName(String userName) {
Object old = this.userName;
this.userName = userName;
this.firePropertyChanged(USER_NAME_PROPERTY, old, userName);
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
Object old = this.password;
this.password = password;
this.firePropertyChanged(PASSWORD_PROPERTY, old, password);
}
public boolean isSavePassword() {
return this.savePassword;
}
public void setSavePassword(boolean savePassword) {
boolean old = this.savePassword;
this.savePassword = savePassword;
this.firePropertyChanged(SAVE_PASSWORD_PROPERTY, old, savePassword);
}
/* NOTE: Driver classpath entries are Strings */
public ListIterator driverClasspathEntries() {
return new CloneListIterator(this.driverClasspathEntries);
}
public int driverClasspathEntriesSize() {
return this.driverClasspathEntries.size();
}
public String getDriverClasspathEntry(int index) {
return (String) this.driverClasspathEntries.get(index);
}
public void addDriverClasspathEntry(int index, String entry) {
this.addItemToList(index, entry, this.driverClasspathEntries, DRIVER_CLASSPATH_ENTRIES_LIST);
}
public void addDriverClasspathEntry(String entry) {
this.addDriverClasspathEntry(this.driverClasspathEntriesSize(), entry);
}
public void addDriverClasspathEntries(int index, List entries) {
this.addItemsToList(index, entries, this.driverClasspathEntries, DRIVER_CLASSPATH_ENTRIES_LIST);
}
public void addDriverClasspathEntries(List entries) {
this.addDriverClasspathEntries(this.driverClasspathEntriesSize(), entries);
}
public void addDriverClasspathEntries(ListIterator entries) {
this.addDriverClasspathEntries(CollectionTools.list(entries));
}
public String removeDriverClasspathEntry(int index) {
return (String) this.removeItemFromList(index, this.driverClasspathEntries, DRIVER_CLASSPATH_ENTRIES_LIST);
}
public List removeDriverClasspathEntries(int index, int length) {
return this.removeItemsFromList(index, length, this.driverClasspathEntries, DRIVER_CLASSPATH_ENTRIES_LIST);
}
public String replaceDriverClasspathEntry(int index, String newEntry) {
return (String) this.setItemInList(index, newEntry, this.driverClasspathEntries, DRIVER_CLASSPATH_ENTRIES_LIST);
}
// ********** problems **********
protected void addProblemsTo(List currentProblems) {
super.addProblemsTo(currentProblems);
if (StringTools.stringIsEmpty(this.getURL())) {
currentProblems.add(this.buildProblem(ProblemConstants.LOGIN_URL_NOT_SPECIFIED, getName()));
}
if (StringTools.stringIsEmpty(this.getDriverClassName())) {
currentProblems.add(this.buildProblem(ProblemConstants.LOGIN_DRIVER_CLASS_NOT_SPECIFIED, getName()));
}
}
// ********** queries **********
public String defaultURL() {
return defaultURLForDriverClassNamed(this.driverClassName);
}
public Iterator candidateURLs() {
return urlsForDriverClassNamed(this.driverClassName);
}
public int candidateURLsSize() {
return urlsForDriverClassNamedSize(this.driverClassName);
}
/**
* candidate URLs are hard-coded
*/
protected void addTransientAspectNamesTo(Set transientAspectNames) {
super.addTransientAspectNamesTo(transientAspectNames);
transientAspectNames.add(CANDIDATE_URLS_COLLECTION);
}
/**
* return the directory used to convert relative
* driver classpath entries to fully qualified files
*/
File driverClasspathBaseDirectory() {
return this.getProject().getSaveDirectory();
}
/**
* return the driver classpath with the entries converted to
* fully qualified files (any relative entries will be
* resolved relative to the project save directory)
*/
public Iterator fullyQualifiedDriverClasspathFiles() {
return new TransformationIterator(this.driverClasspathEntries()) {
protected Object transform(Object next) {
File file = new File((String) next);
if ( ! file.isAbsolute()) {
file = new File(MWLoginSpec.this.driverClasspathBaseDirectory(), file.getPath());
}
return file;
}
};
}
private URL[] driverClasspathURLs() {
List urls = new ArrayList(this.driverClasspathEntriesSize());
for (Iterator stream = this.fullyQualifiedDriverClasspathFiles(); stream.hasNext(); ) {
File file = (File) stream.next();
try {
file = file.getCanonicalFile();
} catch (IOException ioexception) {
// just use the non-canonical file name
}
try {
urls.add(file.toURL());
} catch (IOException ioexception) {
// just don't add it to the list
}
}
return (URL[]) urls.toArray(new URL[urls.size()]);
}
// ********** behavior **********
Driver buildDriver() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
if ((this.driverClassName == null) || (this.driverClassName.length() == 0)) {
throw new IllegalStateException("missing database driver class name");
}
// we should be able to load the driver with only a minimum classpath
ClassLoader classLoader = new URLClassLoader(this.driverClasspathURLs());
Class driverClass = Class.forName(this.driverClassName, true, classLoader);
return (Driver) driverClass.newInstance();
}
/**
* Build and return a TopLink runtime connector for the spec's driver.
*/
public Connector buildConnector() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
return new MWConnector(this.buildDriver(), this.getURL());
}
// ********** runtime conversion **********
private DatabasePlatform databasePlatform() {
return this.getDatabase().getDatabasePlatform();
}
/**
* this is used to log in to the database in the UI
*/
DatabaseLogin buildDevelopmentRuntimeDatabaseLogin() {
DatabaseLogin login = this.buildRuntimeDatabaseLogin();
login.setConnector(this.getDatabase().buildRuntimeConnector());
return login;
}
/**
* this is used to generate deployment XML
*/
DatabaseLogin buildDeploymentRuntimeDatabaseLogin() {
DatabaseLogin login = this.buildRuntimeDatabaseLogin();
if ( ! this.savePassword) {
login.setPassword(null);
}
return login;
}
private DatabaseLogin buildRuntimeDatabaseLogin() {
DatabaseLogin login = new DatabaseLogin();
login.setDriverClassName(this.driverClassName);
login.setDriverURLHeader("");
login.setDatabaseURL(this.url);
login.setUserName(this.userName);
login.setPassword(this.password);
login.setPlatformClassName(this.databasePlatform().getRuntimePlatformClassName());
return login;
}
// ********** displaying and printing **********
public void toString(StringBuffer sb) {
sb.append(this.name);
}
public String displayString() {
return this.name;
}
// ********** TopLink methods **********
private String getPasswordForTopLink() {
return (this.savePassword) ? this.encryptedPassword() : null;
}
private String encryptedPassword() {
return (this.password == null) ? null : getEncryptor().encryptPassword(password);
}
private void setPasswordForTopLink(String password) {
this.password = (password == null) ? null : getEncryptor().decryptPassword(password);
}
/**
* passwords were not encrypted in 4.5 and earlier
*/
private void legacySetPasswordForTopLink(String password) {
this.password = password;
}
/**
* Pair popular JDBC drivers with the typical URLs used
* with them.
*/
// TODO need a better class name
private static final class DriverSpec {
private String driverClassName;
private String[] urls;
DriverSpec(String driverClassName, String[] urls) {
super();
if ((driverClassName == null) || (driverClassName.length() == 0)) {
throw new IllegalArgumentException();
}
this.driverClassName = driverClassName;
if ((urls == null) || (urls.length == 0)) {
throw new IllegalArgumentException();
}
this.urls = urls;
}
DriverSpec(String driverClassName, String url) {
this(driverClassName, new String[] {url});
}
public String getDriverClassName() {
return this.driverClassName;
}
public String[] getUrls() {
return this.urls;
}
public Iterator urls() {
return new ArrayIterator(this.urls);
}
public int urlsSize() {
return this.urls.length;
}
public boolean containsURL(String url) {
for (int i = this.urls.length; i-- > 0; ) {
if (this.urls[i].equals(url)) {
return true;
}
}
return false;
}
public String defaultURL() {
return this.urls[0];
}
public String toString() {
return StringTools.buildToStringFor(this, this.driverClassName);
}
}
/**
* Implement a TopLink runtime Connector that ignores the JDBC
* DriverManager and uses a specified JDBC Driver. This allows us
* to load Drivers dynamically without worrying about which class
* loader loaded the driver and whether it is the same class loader
* that loaded the driver's client.
*
* @see java.sql.DriverManager
*/
private static class MWConnector implements Connector {
private Driver driver;
private String url;
public MWConnector(Driver driver, String url) {
super();
this.driver = driver;
this.url = url;
}
public Connection connect(Properties properties, Session session) {
try {
return this.driver.connect(this.url, properties);
} catch (SQLException ex) {
throw DatabaseException.sqlException(ex);
}
}
public Object clone() {
try {
return super.clone();
} catch (Exception exception) {
throw new InternalError("clone failed");
}
}
public String getConnectionDetails() {
return "MWConnector: " + this.url;
}
public void toString(PrintWriter writer) {
writer.println(this.getConnectionDetails());
}
}
}