package com.webobjects.jdbcadaptor;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import com.webobjects.eoaccess.EOAttribute;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOSynchronizationFactory;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSBundle;
import com.webobjects.foundation.NSData;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSForwardException;
import com.webobjects.foundation.NSLog;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSPropertyListSerialization;
/**
* WO runtime plugin with support for Postgresql.
*
* @author ak
* @author giorgio_v
*/
@SuppressWarnings({ "rawtypes", "unchecked", "deprecation" })
public class DB2PlugIn extends JDBCPlugIn {
private static final String QUERY_STRING_USE_BUNDLED_JDBC_INFO = "useBundledJdbcInfo";
static {
setPlugInNameForSubprotocol(DB2PlugIn.class.getName(), "db2");
}
/**
* Designated constructor.
*/
public DB2PlugIn(JDBCAdaptor adaptor) {
super(adaptor);
}
/**
* Name of the driver.
*/
@Override
public String defaultDriverName() {
return "com.ibm.db2.jcc.DB2Driver";
}
/**
* Name of the database.
*/
@Override
public String databaseProductName() {
return "DB2";
}
/**
* <P>WebObjects 5.4's version of JDBCAdaptor will use this
* in order to assemble the name of the prototype to use when
* it loads models.</P>
* @return the name of the plugin.
*/
@Override
public String name() {
return "DB2";
}
/**
* <P>This method returns true if the connection URL for the
* database has a special flag on it which indicates to the
* system that the jdbcInfo which has been bundled into the
* plugin is acceptable to use in place of actually going to
* the database and getting it.
*/
protected boolean shouldUseBundledJdbcInfo() {
boolean shouldUseBundledJdbcInfo = false;
String url = connectionURL();
if (url != null) {
shouldUseBundledJdbcInfo = url.toLowerCase().matches(".*(\\?|\\?.*&)" + DB2PlugIn.QUERY_STRING_USE_BUNDLED_JDBC_INFO.toLowerCase() + "=(true|yes)(\\&|$)");
}
return shouldUseBundledJdbcInfo;
}
/**
* <P>This is usually extracted from the the database using
* JDBC, but this is really inconvenient for users who are
* trying to generate SQL at some. A specific version of the
* data has been written into the property list of the
* framework and this can be used as a hard-coded equivalent.
* </P>
*/
@Override
public NSDictionary jdbcInfo() {
// you can swap this code out to write the property list out in order
// to get a fresh copy of the JDBCInfo.plist.
// try {
// String jdbcInfoS = NSPropertyListSerialization.stringFromPropertyList(super.jdbcInfo());
// FileOutputStream fos = new FileOutputStream("/tmp/JDBCInfo.plist");
// fos.write(jdbcInfoS.getBytes());
// fos.close();
// }
// catch(Exception e) {
// throw new IllegalStateException("problem writing JDBCInfo.plist",e);
// }
NSDictionary jdbcInfo;
// have a look at the JDBC connection URL to see if the flag has been set to
// specify that the hard-coded jdbcInfo information should be used.
if(shouldUseBundledJdbcInfo()) {
if(NSLog.debugLoggingAllowedForLevel(NSLog.DebugLevelDetailed)) {
NSLog.debug.appendln("Loading jdbcInfo from JDBCInfo.plist as opposed to using the JDBCPlugIn default implementation.");
}
InputStream jdbcInfoStream = NSBundle.bundleForClass(getClass()).inputStreamForResourcePath("JDBCInfo.plist");
if (jdbcInfoStream == null) {
throw new IllegalStateException("Unable to find 'JDBCInfo.plist' in this plugin jar.");
}
try {
jdbcInfo = (NSDictionary) NSPropertyListSerialization.propertyListFromData(new NSData(jdbcInfoStream, 2048), "US-ASCII");
}
catch (IOException e) {
throw new RuntimeException("Failed to load 'JDBCInfo.plist' from this plugin jar.", e);
} finally {
try { jdbcInfoStream.close(); } catch (IOException e) {}
}
}
else {
jdbcInfo = super.jdbcInfo();
}
return jdbcInfo;
}
/**
* Returns a "pure java" synchronization factory.
* Useful for testing purposes.
*/
@Override
public EOSynchronizationFactory createSynchronizationFactory() {
try {
return new DB2SynchronizationFactory(adaptor());
}
catch (Exception e) {
throw new NSForwardException(e, "Couldn't create synchronization factory");
}
}
/**
* Expression class to create. We have custom code, so we need our own class.
*/
@Override
public Class<? extends JDBCExpression> defaultExpressionClass() {
return DB2Expression.class;
}
/**
* Overrides the parent implementation to provide a more efficient mechanism for generating primary keys,
* while generating the primary key support on the fly.
*
* @param count the batch size
* @param entity the entity requesting primary keys
* @param channel open JDBCChannel
* @return NSArray of NSDictionary where each dictionary corresponds to a unique primary key value
*/
@Override
public NSArray newPrimaryKeys(int count, EOEntity entity, JDBCChannel channel) {
if (isPrimaryKeyGenerationNotSupported(entity)) {
return null;
}
EOAttribute attribute = entity.primaryKeyAttributes().lastObject();
String attrName = attribute.name();
boolean isIntType = "i".equals(attribute.valueType());
NSMutableArray results = new NSMutableArray(count);
String sequenceName = sequenceNameForEntity(entity);
DB2Expression expression = new DB2Expression(entity);
boolean succeeded = false;
for (int tries = 0; !succeeded && tries < 2; tries++) {
while (results.count() < count) {
try {
StringBuilder sql = new StringBuilder();
sql.append("SELECT next value for ");
sql.append(sequenceName);
sql.append(" AS KEY from sysibm.sysdummy1");
expression.setStatement(sql.toString());
channel.evaluateExpression(expression);
try {
NSDictionary row;
while ((row = channel.fetchRow()) != null) {
Enumeration pksEnum = row.allValues().objectEnumerator();
while (pksEnum.hasMoreElements()) {
Number pkObj = (Number)pksEnum.nextElement();
Number pk;
if (isIntType) {
pk = Integer.valueOf(pkObj.intValue());
}
else {
pk = Long.valueOf(pkObj.longValue());
}
results.addObject(new NSDictionary(pk, attrName));
}
}
}
finally {
channel.cancelFetch();
}
succeeded = true;
}
catch (JDBCAdaptorException ex) {
throw ex;
}
}
}
if (results.count() != count) {
throw new IllegalStateException("Unable to generate primary keys from the sequence for " + entity + ".");
}
return results;
}
/**
* Utility method that returns the name of the sequence associated
* with <code>entity</code>. Non Static so we can reuse code.
*
* @param entity the entity
* @return the name of the sequence
*/
protected String sequenceNameForEntity(EOEntity entity) {
return _sequenceNameForEntity(entity);
}
/**
* Utility method that returns the name of the sequence associated
* with <code>entity</code>
*
* @param entity the entity
* @return the name of the sequence
*/
protected static String _sequenceNameForEntity(EOEntity entity) {
return entity.primaryKeyRootName() + "_seq";
}
/**
* Checks whether primary key generation can be supported for <code>entity</code>
*
* @param entity the entity to be checked
* @return yes/no
*/
protected boolean isPrimaryKeyGenerationNotSupported(EOEntity entity) {
return entity.primaryKeyAttributes().count() > 1 || entity.primaryKeyAttributes().lastObject().adaptorValueType() != EOAttribute.AdaptorNumberType;
}
}