package com.webobjects.jdbcadaptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.sql.Blob;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.webobjects.eoaccess.EOAdaptor;
import com.webobjects.eoaccess.EOAttribute;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOJoin;
import com.webobjects.eoaccess.EOModel;
import com.webobjects.eoaccess.EOModelGroup;
import com.webobjects.eoaccess.EORelationship;
import com.webobjects.eoaccess.EOSQLExpression;
import com.webobjects.eoaccess.synchronization.EOSchemaGenerationOptions;
import com.webobjects.eoaccess.synchronization.EOSchemaSynchronizationFactory;
import com.webobjects.eocontrol.EOFetchSpecification;
import com.webobjects.eocontrol.EOQualifier;
import com.webobjects.eocontrol.EOSortOrdering;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSBundle;
import com.webobjects.foundation.NSData;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSLog;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSProperties;
import com.webobjects.foundation.NSPropertyListSerialization;
import com.webobjects.foundation.NSRange;
import com.webobjects.foundation.NSSelector;
import com.webobjects.foundation._NSStringUtilities;
public class _MySQLPlugIn extends JDBCPlugIn {
private static final String DriverClassName = "com.mysql.jdbc.Driver";
private static final String DriverProductName = "MySQL";
private static final String QUERY_STRING_USE_BUNDLED_JDBC_INFO = "useBundledJdbcInfo";
public _MySQLPlugIn(JDBCAdaptor adaptor) {
super(adaptor);
}
public static class MySQLExpression extends JDBCExpression {
// Lazy initialized constants
private static class CONFIG {
// Turning on identifier quoting allows the use of reserved words for identifier (table, field, etc.) names
final static boolean ENABLE_IDENTIFIER_QUOTING = Boolean.getBoolean("com.webobjects.jdbcadaptor.MySQLExpression.enableIdentifierQuoting");
// Inserts "\n\t" between statement clauses for log readability. Useful in development
final static boolean LINE_PER_CLAUSE = Boolean.getBoolean("com.webobjects.jdbcadaptor.MySQLExpression.enableLinePerClause");
// Length values for the string constant elements of the statement taking into account LINE_PER_CLAUSE for development and/or MySQL log readability.
// Note that the space is needed before FROM, WHERE etc in the LINE_PER_CLAUSE variant to ensure compatibility with code that assumes a space
// surrounding the FROM, as in er.extensions.jdbc.ERXSQLHelper.rowCountForFetchSpecification(...) for example
final static String FROM_STRING = ( CONFIG.LINE_PER_CLAUSE ? "\n\t FROM " : " FROM " );
final static String WHERE_STRING = ( CONFIG.LINE_PER_CLAUSE ? "\n\t WHERE " : " WHERE " );
final static String ORDER_BY_STRING = ( CONFIG.LINE_PER_CLAUSE ? "\n\t ORDER BY " : " ORDER BY " );
final static String LIMIT_STRING = ( CONFIG.LINE_PER_CLAUSE ? "\n\t LIMIT " : " LIMIT " );
final static int FROM_LENGTH = FROM_STRING.length();
final static int WHERE_LENGTH = WHERE_STRING.length();
final static int ORDER_BY_LENGTH = ORDER_BY_STRING.length();
final static int LIMIT_LENGTH = LIMIT_STRING.length();
/**
* From the MySQL Manual: "An identifier may be quoted or
* unquoted. If an identifier contains special characters or is a
* reserved word, you must quote it whenever you refer to it. ...
* The identifier quote character is the backtick."
*/
final static String IDENTIFIER_QUOTE_CHARACTER = (ENABLE_IDENTIFIER_QUOTING ? "`" : "");
}
private int _fetchLimit;
private NSRange _fetchRange;
private final NSSelector<NSRange> _fetchRangeSelector = new NSSelector<NSRange>("fetchRange");
/**
* Holds array of join clause definitions
*/
private final NSMutableArray<JoinClauseDefinition> _alreadyJoined = new NSMutableArray<JoinClauseDefinition>();
public MySQLExpression(EOEntity entity) {
super(entity);
}
/**
* http://dev.mysql.com/doc/refman/5.5/en/string-comparison-functions.html
*
* @see com.webobjects.eoaccess.EOSQLExpression#sqlEscapeChar()
*/
@Override
public char sqlEscapeChar(){
return '|';
}
/**
* Overridden because MySQL does not use the default quote character in
* EOSQLExpression.externalNameQuoteCharacter() which is an empty
* string.
*
* Note that quoting is disabled by default and can be enabled by setting property <code>com.webobjects.jdbcadaptor.MySQLExpression.enableIdentifierQuoting</code> to true.
*/
@Override
public String externalNameQuoteCharacter() {
return CONFIG.IDENTIFIER_QUOTE_CHARACTER;
}
/**
* Overriding super here so we can grab a fetch range or fetch limit if specified in the EOFetchSpecification. If a fetchRange method
* returning an NSRange exists in the EOFetchSpecification subclass being passed in, the the fetchLimit will be ignored.
*
* @see com.webobjects.jdbcadaptor.JDBCExpression#prepareSelectExpressionWithAttributes(NSArray, boolean, EOFetchSpecification)
*/
@Override
public void prepareSelectExpressionWithAttributes(NSArray<EOAttribute> attributes, boolean lock,
EOFetchSpecification fetchSpec) {
try {
_fetchRange = _fetchRangeSelector.invoke(fetchSpec);
// We will get an error when not using our custom ERXFetchSpecification subclass
// We could have added ERExtensions to the classpath and checked for instanceof, but I thought
// this is a little cleaner since people may be using this PlugIn and not Wonder in some legacy apps.
} catch (IllegalArgumentException e) {
;
} catch (IllegalAccessException e) {
;
} catch (InvocationTargetException e) {
;
} catch (NoSuchMethodException e) {
;
}
// Only check for fetchLimit of fetchRange is not provided.
if (_fetchRange == null && !fetchSpec.promptsAfterFetchLimit()) {
_fetchLimit = fetchSpec.fetchLimit();
}
super.prepareSelectExpressionWithAttributes(attributes, lock, fetchSpec);
}
/**
* Overriding to
* <ul>
* <li>add LIMIT clause if _fetchLimit > 0</li>
* <li>support MySQL JOIN syntax (similar syntax to what PostgreSQL PlugIn generates)</li>
* </ul>
*
* @see com.webobjects.eoaccess.EOSQLExpression#assembleSelectStatementWithAttributes(NSArray, boolean, EOQualifier, NSArray, java.lang.String, String, String, String, String, String, String)
*/
@Override
public String assembleSelectStatementWithAttributes(@SuppressWarnings("rawtypes") NSArray/*<EOAttribute>*/ attributes, boolean lock, EOQualifier qualifier,
@SuppressWarnings("rawtypes") NSArray fetchOrder, String selectString, String columnList, String tableList, String whereClause,
String joinClause, String orderByClause, String lockClause) {
// When we are selecting from a single table, the joinClause will be empty and the tableList will contain the single table reference.
// When we have joins, then both the joinClause and tableList will be passed in, however we will just be using the joinClause in the FROM clause.
int size = selectString.length() + columnList.length() + CONFIG.FROM_LENGTH;
if ((lockClause != null) && (lockClause.length() != 0)) {
size += lockClause.length() + 1;
}
if ((whereClause != null) && (whereClause.length() != 0)) {
size += (whereClause.length() + CONFIG.WHERE_LENGTH);
}
if ((joinClause != null) && (joinClause.length() != 0)) {
size += joinClause.length();
} else {
size += tableList.length();
}
if ((orderByClause != null) && (orderByClause.length() != 0)) {
size += (orderByClause.length() + CONFIG.ORDER_BY_LENGTH);
}
// If necessary, create LIMIT clause and add to buffer size
String limitClause = null;
// fetchRange override fetchLimit
if (_fetchRange != null) {
limitClause = _fetchRange.location() + ", " + _fetchRange.length();
size += CONFIG.LIMIT_LENGTH + limitClause.length();
} else if (_fetchLimit > 0) {
limitClause = Integer.toString(_fetchLimit);
size += CONFIG.LIMIT_LENGTH + limitClause.length();
}
StringBuilder buffer = new StringBuilder(size);
buffer.append(selectString);
buffer.append(columnList);
buffer.append(CONFIG.FROM_STRING);
if (joinClause != null && joinClause.length() > 0) {
buffer.append(joinClause);
} else {
buffer.append(tableList);
}
if (whereClause != null && whereClause.length() > 0) {
buffer.append(CONFIG.WHERE_STRING);
buffer.append(whereClause);
}
if ((orderByClause != null) && (orderByClause.length() != 0)) {
buffer.append(CONFIG.ORDER_BY_STRING);
buffer.append(orderByClause);
}
// Add limit clause
if (limitClause != null) {
buffer.append(CONFIG.LIMIT_STRING);
buffer.append(limitClause);
}
if ((lockClause != null) && (lockClause.length() != 0)) {
buffer.append(' ');
buffer.append(lockClause);
}
return buffer.toString();
}
/**
* Overriden to contruct a valid SQL92 JOIN clause as opposed to the
* Oracle-like SQL the superclass produces.
*
* kieran copied from PostgresqlExpression
*/
@Override
public String joinClauseString() {
NSMutableDictionary<String, Boolean> seenIt = new NSMutableDictionary<String, Boolean>();
StringBuilder sb = new StringBuilder();
JoinClauseDefinition jc;
EOSortOrdering.sortArrayUsingKeyOrderArray(_alreadyJoined, new NSArray<EOSortOrdering>(EOSortOrdering.sortOrderingWithKey("sortKey", EOSortOrdering.CompareAscending)));
if (_alreadyJoined.count() > 0) {
jc = _alreadyJoined.objectAtIndex(0);
sb.append(jc);
seenIt.setObjectForKey(Boolean.TRUE, jc._table1);
seenIt.setObjectForKey(Boolean.TRUE, jc._table2);
}
for (int i = 1; i < _alreadyJoined.count(); i++) {
jc = _alreadyJoined.objectAtIndex(i);
sb.append(jc._op);
if (seenIt.objectForKey(jc._table1) == null) {
sb.append(jc._table1);
seenIt.setObjectForKey(Boolean.TRUE, jc._table1);
} else if (seenIt.objectForKey(jc._table2) == null) {
sb.append(jc._table2);
seenIt.setObjectForKey(Boolean.TRUE, jc._table2);
}
sb.append(jc._joinCondition);
}
return sb.toString();
}
/**
* Override so that the joinClause can be constructed after super has
* iterated though all joins and called our assembleJoinClause to create
* our array of JoinClauseDefinitions.
*
* @see com.webobjects.eoaccess.EOSQLExpression#joinExpression()
*/
@Override
public void joinExpression() {
super.joinExpression();
if (_alreadyJoined.count() > 0) {
_joinClauseString = joinClauseString();
} else {
_joinClauseString = null;
}
}
/**
* This is called by super for each join. We do not actually construct the join clause as we get called since
* for SQL92 JOIN syntax we need to know about all joins before we construct the complete join clause.
*
* The objective of this implementation is to insert a new unique {@link JoinClauseDefinition} into
* the <code>_alreadyJoined</code> array of {@link JoinClauseDefinition} objects.
*
* The join clause itself is assembled by <code>joinClauseString()</code>.
*
* @param leftName
* the table name on the left side of the clause
* @param rightName
* the table name on the right side of the clause
* @param semantic
* the join semantic
* @return the join clause
*
* kieran based this on logic from PostgresqlExpression
*/
@SuppressWarnings("unchecked")
@Override
public String assembleJoinClause(String leftName, String rightName, int semantic) {
if (!useAliases()) {
return super.assembleJoinClause(leftName, rightName, semantic);
}
String leftAlias = leftName.substring(0, leftName.indexOf("."));
String rightAlias = rightName.substring(0, rightName.indexOf("."));
NSArray<String> k;
EOEntity rightEntity;
EOEntity leftEntity;
String relationshipKey = null;
EORelationship r;
if (leftAlias.equals("t0")) {
leftEntity = entity();
} else {
k = aliasesByRelationshipPath().allKeysForObject(leftAlias);
relationshipKey = k.count() > 0 ? (String) k.lastObject() : "";
leftEntity = entityForKeyPath(relationshipKey);
}
if (rightAlias.equals("t0")) {
rightEntity = entity();
} else {
k = aliasesByRelationshipPath().allKeysForObject(rightAlias);
relationshipKey = k.count() > 0 ? (String) k.lastObject() : "";
rightEntity = entityForKeyPath(relationshipKey);
}
int dotIndex = relationshipKey.indexOf(".");
relationshipKey = dotIndex == -1 ? relationshipKey : relationshipKey.substring(relationshipKey.lastIndexOf(".") + 1);
r = rightEntity.anyRelationshipNamed(relationshipKey);
// fix from Michael Müller for the case Foo.fooBars.bar has a
// Bar.foo relationship (instead of Bar.foos)
if (r == null || r.destinationEntity() != leftEntity) {
r = leftEntity.anyRelationshipNamed(relationshipKey);
}
// timc 2006-02-26 IMPORTANT or quotes are ignored and mixed case
// field names won't work
String rightTable;
String leftTable;
if (CONFIG.ENABLE_IDENTIFIER_QUOTING) {
rightTable = rightEntity.valueForSQLExpression(this);
leftTable = leftEntity.valueForSQLExpression(this);
} else {
rightTable = rightEntity.externalName();
leftTable = leftEntity.externalName();
}
// We need the numeric table by removing the leading 't' or 'T' from the table alias
int leftTableID = Integer.parseInt(leftAlias.substring(1));
// Compute left and right table references
String leftTableNameAndAlias = leftTable + " " + leftAlias;
String rightTableNameAndAlias = rightTable + " " + rightAlias;
// COmpute joinOperation
String joinOperation = null;
switch (semantic) {
case EORelationship.LeftOuterJoin:
// LEFT OUTER JOIN and LEFT JOIN are equivalent in MySQL
joinOperation = " LEFT JOIN ";
break;
case EORelationship.RightOuterJoin:
// RIGHT OUTER JOIN and RIGHT JOIN are equivalent in MySQL
joinOperation = " RIGHT JOIN ";
break;
case EORelationship.FullOuterJoin:
throw new IllegalArgumentException("Unfortunately MySQL does not support FULL OUTER JOIN that is specified for " + leftName + " joining " + rightName + "!");
//jc.op = " FULL OUTER JOIN ";
//break;
case EORelationship.InnerJoin:
// INNER JOIN and JOIN are equivalent in MySQL
joinOperation = " JOIN ";
break;
}
// Compute joinCondition
NSArray<EOJoin> joins = r.joins();
int joinsCount = joins.count();
NSMutableArray<String> joinStrings = new NSMutableArray<String>(joinsCount);
for (int i = 0; i < joinsCount; i++) {
EOJoin currentJoin = joins.objectAtIndex(i);
String left;
String right;
if (CONFIG.ENABLE_IDENTIFIER_QUOTING) {
left = leftAlias + "." + sqlStringForSchemaObjectName(currentJoin.sourceAttribute().columnName());
right = rightAlias + "." + sqlStringForSchemaObjectName(currentJoin.destinationAttribute().columnName());
} else {
left = leftAlias + "." + currentJoin.sourceAttribute().columnName();
right = rightAlias + "." + currentJoin.destinationAttribute().columnName();
}
joinStrings.addObject(left + " = " + right);
}
String joinCondition = " ON " + joinStrings.componentsJoinedByString(" AND ");
JoinClauseDefinition jc = new JoinClauseDefinition(leftTableNameAndAlias, joinOperation, rightTableNameAndAlias, joinCondition, leftTableID);
if (!_alreadyJoined.containsObject(jc)) {
_alreadyJoined.insertObjectAtIndex(jc, 0);
}
return null;
}
/**
* Utility that traverses a key path to find the last destination entity
*
* @param keyPath
* the key path
* @return the entity at the end of the keypath
*/
private EOEntity entityForKeyPath(String keyPath) {
NSArray<String> keys = NSArray.componentsSeparatedByString(keyPath, ".");
EOEntity ent = entity();
for (int i = 0; i < keys.count(); i++) {
String k = keys.objectAtIndex(i);
EORelationship rel = ent.anyRelationshipNamed(k);
if (rel == null) {
// it may be an attribute
if (ent.anyAttributeNamed(k) != null) {
break;
}
throw new IllegalArgumentException("relationship " + keyPath + " generated null");
}
ent = rel.destinationEntity();
}
return ent;
}
/**
* Overriden to not call the super implementation. This simply calls our custom assembleJoinClause
*
* @param leftName
* the table name on the left side of the clause
* @param rightName
* the table name on the right side of the clause
* @param semantic
* the join semantic
*
* kieran copied from PostgresqlExpression
*/
@Override
public void addJoinClause(String leftName, String rightName, int semantic) {
assembleJoinClause(leftName, rightName, semantic);
}
/**
* Helper class that stores a join definition and helps
* <code>MySQLExpression</code> to assemble the correct join
* clause.
*
* kieran copied from PostgreSQLPlugIn's JoinClause helper class
*/
public static final class JoinClauseDefinition {
private final String _table1;
private final String _op;
private final String _table2;
private final String _joinCondition;
private final int _leftTableID;
private final String _toString;
public JoinClauseDefinition(String leftTableNameAndAlias, String joinOperation, String rightTableNameAndAlias, String joinCondition2, int leftTableID) {
_table1 = leftTableNameAndAlias;
_op = joinOperation;
_table2 = rightTableNameAndAlias;
_joinCondition = joinCondition2;
_leftTableID = leftTableID;
_toString = _table1 + _op + _table2 + _joinCondition;
}
@Override
public String toString() {
return _toString;
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof JoinClauseDefinition)) {
return false;
}
return _toString.equals(obj.toString());
}
/* Effective Java #9 : Must override hashCode when overriding equals.
*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return _toString.hashCode() + 43;
}
/**
* Property that makes this class "sortable" by left table ID. Needed to correctly
* assemble a join clause.
*
* @return sort key
*/
public int sortKey() {
return _leftTableID;
}
}
}
public static class MySQLSynchronizationFactory extends EOSchemaSynchronizationFactory {
public MySQLSynchronizationFactory(EOAdaptor adaptor) {
super(adaptor);
}
@Override
public String _alterPhraseInsertionClausePrefixAtIndex(int columnIndex) {
return (columnIndex != 0)?"":" ADD ";
}
@Override
protected String formatTableName(String name) {
return name;
}
@Override
protected String formatColumnName(String name) {
return name;
}
@Override
public NSArray<EOSQLExpression> statementsToConvertColumnType(String columnName, String tableName, ColumnTypes oldType, ColumnTypes newType, EOSchemaGenerationOptions options) {
String columnTypeString = statementToCreateDataTypeClause(newType);
StringBuilder sb = new StringBuilder();
sb.append("ALTER TABLE ").append(formatTableName(tableName));
sb.append(" MODIFY ").append(formatColumnName(columnName));
sb.append(' ').append(columnTypeString);
NSArray<EOSQLExpression> statements = new NSArray<EOSQLExpression>(_expressionForString(sb.toString()));
return statements;
}
@Override
public NSArray<EOSQLExpression> primaryKeySupportStatementsForEntityGroups(NSArray<NSArray<EOEntity>> entityGroups) {
String pkTable = ((JDBCAdaptor)adaptor()).plugIn().primaryKeyTableName();
NSMutableArray<EOSQLExpression> statements = new NSMutableArray<EOSQLExpression>();
statements.addObject(_expressionForString(new StringBuilder().append("CREATE TABLE ").append(pkTable).append(" (NAME CHAR(40) PRIMARY KEY, PK INT)").toString()));
return statements;
}
@Override
public NSArray<EOSQLExpression> statementsToDeleteColumnNamed(String columnName, String tableName, EOSchemaGenerationOptions options) {
return new NSArray<EOSQLExpression>(_expressionForString(new StringBuilder().append("ALTER TABLE ").append(tableName).append(" DROP COLUMN ").append(columnName).toString()));
}
@Override
public NSArray<EOSQLExpression> statementsToInsertColumnForAttribute(EOAttribute attribute, EOSchemaGenerationOptions options) {
String columnCreationClause = _columnCreationClauseForAttribute(attribute);
return new NSArray<EOSQLExpression>(_expressionForString(new StringBuilder().append("ALTER TABLE ").append(attribute.entity().externalName()).append(_alterPhraseInsertionClausePrefixAtIndex(0)).append(columnCreationClause).toString()));
}
@Override
public NSArray<EOSQLExpression> statementsToModifyColumnNullRule(String columnName, String tableName, boolean allowsNull, EOSchemaGenerationOptions options) {
String nullStatement = allowsNull ? " NULL" : " NOT NULL";
EOAttribute attribute = attributeInEntityWithColumnName(entityForTableName(tableName), columnName);
String externalType = columnTypeStringForAttribute(attribute);
return new NSArray<EOSQLExpression>(_expressionForString(new StringBuilder().append("ALTER TABLE ").append(formatTableName(tableName)).append(" MODIFY ").append(formatColumnName(columnName)).append(' ').append(externalType).append(nullStatement).toString()));
}
@Override
public boolean supportsDirectColumnNullRuleModification() {
return true;
}
@Override
public NSArray<EOSQLExpression> statementsToRenameColumnNamed(String columnName, String tableName, String newName, EOSchemaGenerationOptions options) {
EOAttribute attribute = attributeInEntityWithColumnName(entityForTableName(tableName), newName);
String nullStatement = attribute.allowsNull() ? " NULL" : " NOT NULL";
String externalType = columnTypeStringForAttribute(attribute);
return new NSArray<EOSQLExpression>(_expressionForString(new StringBuilder().append("ALTER TABLE ").append(formatTableName(tableName)).append(" CHANGE ").append(formatColumnName(columnName)).append(' ').append(formatColumnName(newName)).append(' ').append(externalType).append(nullStatement).toString()));
}
@Override
public boolean supportsDirectColumnRenaming() {
return true;
}
private EOEntity entityForTableName(String tableName) {
EOModelGroup modelGroup = EOModelGroup.globalModelGroup();
for (EOModel model : modelGroup.models()) {
for (EOEntity entity : model.entities()) {
if (entity.externalName() != null && entity.externalName().equalsIgnoreCase(tableName)) {
return entity;
}
}
}
return null;
}
@Override
public NSArray<EOSQLExpression> dropPrimaryKeySupportStatementsForEntityGroups(NSArray<NSArray<EOEntity>> entityGroups) {
return new NSArray<EOSQLExpression>(_expressionForString(new StringBuilder().append("DROP TABLE ").append(((JDBCAdaptor)adaptor()).plugIn().primaryKeyTableName()).append(" CASCADE").toString()));
}
@Override
public NSArray<EOSQLExpression> _statementsToDropPrimaryKeyConstraintsOnTableNamed(String tableName) {
return new NSArray<EOSQLExpression>(_expressionForString(new StringBuilder().append("alter table ").append(tableName).append(" drop primary key").toString()));
}
@Override
public NSArray<EOSQLExpression> foreignKeyConstraintStatementsForRelationship(EORelationship relationship) {
return null;
}
@Override
public NSArray<EOSQLExpression> statementsToRenameTableNamed(String tableName, String newName, EOSchemaGenerationOptions options) {
return new NSArray<EOSQLExpression>(_expressionForString(new StringBuilder().append("rename table ").append(tableName).append(" to ").append(newName).toString()));
}
@Override
public boolean supportsSchemaSynchronization() {
return true;
}
// Shameless stolen from PostresqlSynchronizationFactory - davidleber
//
// I blame statementstToConvertColumnType for not taking a damn EOAttribute for
// having to steal this from EOSQLExpression
public String columnTypeStringForAttribute(EOAttribute attribute) {
if (attribute.precision() != 0) {
String precision = String.valueOf(attribute.precision());
String scale = String.valueOf(attribute.scale());
return _NSStringUtilities.concat(attribute.externalType(), "(", precision, ",", scale, ")");
}
if (attribute.width() != 0) {
String width = String.valueOf(attribute.width());
return _NSStringUtilities.concat(attribute.externalType(), "(", width, ")");
}
return attribute.externalType();
}
private String statementToCreateDataTypeClause(ColumnTypes columntypes) {
int size = columntypes.precision();
if (size == 0) {
size = columntypes.width();
}
if (size == 0) {
return columntypes.name();
}
int scale = columntypes.scale();
if (scale == 0) {
return columntypes.name() + "(" + size + ")";
}
return columntypes.name() + "(" + size + "," + scale + ")";
}
}
/**
* <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 Name of the plugin.
*/
@Override
public String name() {
return DriverProductName;
}
@Override
public String defaultDriverName() {
return DriverClassName;
}
@Override
public String databaseProductName() {
return DriverProductName;
}
@Override
public Class<? extends JDBCExpression> defaultExpressionClass() {
try {
if (NSProperties.booleanForKey("com.webobjects.jdbcadaptor.MySQLExpression.enable")) {
return com.webobjects.jdbcadaptor.MySQLPlugIn.MySQLExpression.class;
}
} catch (NullPointerException ex) {
// property was not set
}
return com.webobjects.jdbcadaptor._MySQLPlugIn.MySQLExpression.class;
}
@Override
public EOSchemaSynchronizationFactory createSchemaSynchronizationFactory() {
return new com.webobjects.jdbcadaptor._MySQLPlugIn.MySQLSynchronizationFactory(_adaptor);
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public NSDictionary<String, Object> jdbcInfo() {
NSDictionary<String, Object> 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<String, Object>) 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 {
NSMutableDictionary<String, Object> mutableInfo = super.jdbcInfo().mutableClone();
NSMutableDictionary<String, NSDictionary> typeInfo = ((NSDictionary<String, NSDictionary>)mutableInfo.objectForKey("typeInfo")).mutableClone();
NSDictionary textTypeInfo = typeInfo.objectForKey("TEXT");
if(textTypeInfo != null) {
Object rawCreateParams = textTypeInfo.objectForKey("createParams");
if(!rawCreateParams.equals("1")) {
NSMutableDictionary newRawTypeInfo = textTypeInfo.mutableClone();
newRawTypeInfo.setObjectForKey("1", "createParams");
typeInfo.setObjectForKey(newRawTypeInfo, "RAW");
}
}
JDBCPlugIn._takeValueForKeyPath(typeInfo, "0", "BLOB", "createParams");
JDBCPlugIn._takeValueForKeyPath(typeInfo, "0", "LONGBLOB", "createParams");
JDBCPlugIn._takeValueForKeyPath(typeInfo, "0", "MEDIUMBLOB", "createParams");
JDBCPlugIn._takeValueForKeyPath(typeInfo, "0", "TINYBLOB", "createParams");
mutableInfo.setObjectForKey(typeInfo, "typeInfo");
NSLog.debug.appendln(
new StringBuilder("fetched MySQL (")
.append(databaseProductName())
.append(") JDBC Info = ")
.append(mutableInfo)
.toString()
);
// Write a fresh copy of JDBCInfo.plist to /tmp
//writeJDBCInfo(mutableInfo);
jdbcInfo = mutableInfo.immutableClone();
}
return jdbcInfo;
}
@Override
public Object fetchBLOB(ResultSet rs, int column, EOAttribute attribute, boolean materialize) throws SQLException {
NSData data = null;
Blob blob = rs.getBlob(column);
if(blob == null) { return null; }
if(!materialize) { return blob; }
InputStream stream = blob.getBinaryStream();
try {
int chunkSize = (int)blob.length();
if(chunkSize == 0) {
data = NSData.EmptyData;
} else {
data = new NSData(stream, chunkSize);
}
} catch(IOException e) {
throw new JDBCAdaptorException(e.getMessage(), null);
} finally {
try {if(stream != null) stream.close(); } catch(IOException e) { /* Nothing we can do */ };
}
return data;
}
/**
* <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. Default is false.
* @return the flag set on the jdbc url with 'useBundledJdbcInfo'
*/
protected boolean shouldUseBundledJdbcInfo() {
boolean shouldUseBundledJdbcInfo = false;
String url = connectionURL();
if (url != null) {
shouldUseBundledJdbcInfo = url.toLowerCase().matches(".*(\\?|\\?.*&)" + _MySQLPlugIn.QUERY_STRING_USE_BUNDLED_JDBC_INFO.toLowerCase() + "=(true|yes)(\\&|$)");
}
return shouldUseBundledJdbcInfo;
}
protected void writeJDBCInfo(NSDictionary<String, Object> jdbcInfo) {
try {
String jdbcInfoS = NSPropertyListSerialization.stringFromPropertyList(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);
}
}
}