diff --git a/symmetric/symmetric-assemble/pom.xml b/symmetric/symmetric-assemble/pom.xml index f84bd0cd00..0fb4956492 100644 --- a/symmetric/symmetric-assemble/pom.xml +++ b/symmetric/symmetric-assemble/pom.xml @@ -28,8 +28,10 @@ + ../symmetric-util ../symmetric-csv - ../symmetric-ddl + ../symmetric-ddl + ../symmetric-io ../symmetric-core ../symmetric-server diff --git a/symmetric/symmetric-core/pom.xml b/symmetric/symmetric-core/pom.xml index 3b3605b1a4..b7cce8e2a8 100644 --- a/symmetric/symmetric-core/pom.xml +++ b/symmetric/symmetric-core/pom.xml @@ -20,7 +20,7 @@ org.jumpmind.symmetric - symmetric-ddl + symmetric-io commons-dbcp diff --git a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/AbstractDbDialect.java b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/AbstractDbDialect.java index e05fe5f771..7509612627 100644 --- a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/AbstractDbDialect.java +++ b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/AbstractDbDialect.java @@ -57,6 +57,7 @@ import org.jumpmind.symmetric.common.ParameterConstants; import org.jumpmind.symmetric.common.logging.ILog; import org.jumpmind.symmetric.common.logging.LogFactory; +import org.jumpmind.symmetric.db.DmlStatement.DmlType; import org.jumpmind.symmetric.db.interbase.InterbaseDbDialect; import org.jumpmind.symmetric.db.sybase.SybaseDbDialect; import org.jumpmind.symmetric.ddl.Platform; @@ -69,8 +70,6 @@ import org.jumpmind.symmetric.ddl.platform.SqlBuilder; import org.jumpmind.symmetric.ext.IDatabaseUpgradeListener; import org.jumpmind.symmetric.load.IColumnFilter; -import org.jumpmind.symmetric.load.StatementBuilder; -import org.jumpmind.symmetric.load.StatementBuilder.DmlType; import org.jumpmind.symmetric.model.Channel; import org.jumpmind.symmetric.model.DataEventType; import org.jumpmind.symmetric.model.Node; @@ -1372,9 +1371,9 @@ public void addDatabaseUpgradeListener(IDatabaseUpgradeListener listener) { protected void initLobHandler() { } - public StatementBuilder createStatementBuilder(DmlType type, String tableName, Column[] keys, + public DmlStatement createStatementBuilder(DmlType type, String catalogName, String schemaName, String tableName, Column[] keys, Column[] columns, Column[] preFilteredColumns) { - return new StatementBuilder(type, tableName, keys, columns, preFilteredColumns, + return new DmlStatement(type, catalogName, schemaName, tableName, keys, columns, preFilteredColumns, isDateOverrideToTimestamp(), getIdentifierQuoteString()); } diff --git a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/AutoIncrementColumnFilter.java b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/AutoIncrementColumnFilter.java index 156925df25..4543cb1a18 100644 --- a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/AutoIncrementColumnFilter.java +++ b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/AutoIncrementColumnFilter.java @@ -24,11 +24,11 @@ import java.util.ArrayList; import org.apache.commons.collections.CollectionUtils; +import org.jumpmind.symmetric.db.DmlStatement.DmlType; import org.jumpmind.symmetric.ddl.model.Column; import org.jumpmind.symmetric.ddl.model.Table; import org.jumpmind.symmetric.load.IColumnFilter; import org.jumpmind.symmetric.load.IDataLoaderContext; -import org.jumpmind.symmetric.load.StatementBuilder.DmlType; public class AutoIncrementColumnFilter implements IColumnFilter { diff --git a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/IDbDialect.java b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/IDbDialect.java index 22765be999..54b8adcf05 100644 --- a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/IDbDialect.java +++ b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/IDbDialect.java @@ -23,14 +23,13 @@ import java.util.Map; import java.util.Set; +import org.jumpmind.symmetric.db.DmlStatement.DmlType; import org.jumpmind.symmetric.ddl.Platform; import org.jumpmind.symmetric.ddl.model.Column; import org.jumpmind.symmetric.ddl.model.Database; import org.jumpmind.symmetric.ddl.model.Table; import org.jumpmind.symmetric.ext.IDatabaseUpgradeListener; import org.jumpmind.symmetric.load.IColumnFilter; -import org.jumpmind.symmetric.load.StatementBuilder; -import org.jumpmind.symmetric.load.StatementBuilder.DmlType; import org.jumpmind.symmetric.model.Channel; import org.jumpmind.symmetric.model.DataEventType; import org.jumpmind.symmetric.model.Node; @@ -325,7 +324,7 @@ public long insertWithGeneratedKey(JdbcTemplate jdbcTemplate, final String sql, public String getDriverVersion(); - public StatementBuilder createStatementBuilder(DmlType type, String tableName, Column[] keys, Column[] columns, + public DmlStatement createStatementBuilder(DmlType type, String catalogName, String schemaName, String tableName, Column[] keys, Column[] columns, Column[] preFilteredColumns); /* diff --git a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/postgresql/PostgreSqlDbDialect.java b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/postgresql/PostgreSqlDbDialect.java index 4dbcd0f3e4..fb2f3d61f2 100644 --- a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/postgresql/PostgreSqlDbDialect.java +++ b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/postgresql/PostgreSqlDbDialect.java @@ -33,11 +33,11 @@ import org.jumpmind.symmetric.common.ParameterConstants; import org.jumpmind.symmetric.db.AbstractDbDialect; import org.jumpmind.symmetric.db.BinaryEncoding; +import org.jumpmind.symmetric.db.DmlStatement; import org.jumpmind.symmetric.db.IDbDialect; +import org.jumpmind.symmetric.db.DmlStatement.DmlType; import org.jumpmind.symmetric.ddl.Platform; import org.jumpmind.symmetric.ddl.model.Column; -import org.jumpmind.symmetric.load.StatementBuilder; -import org.jumpmind.symmetric.load.StatementBuilder.DmlType; import org.jumpmind.symmetric.model.Trigger; import org.jumpmind.symmetric.model.TriggerHistory; import org.springframework.jdbc.core.JdbcTemplate; @@ -277,9 +277,9 @@ protected String cleanTextForTextBasedColumns(String text) { } @Override - public StatementBuilder createStatementBuilder(DmlType type, String tableName, Column[] keys, + public DmlStatement createStatementBuilder(DmlType type, String catalogName, String schemaName, String tableName, Column[] keys, Column[] columns, Column[] preFilteredColumns) { - return new PostgresSqlStatementBuilder(type, tableName, keys, + return new PostgresDmlStatement(type, catalogName, schemaName, tableName, keys, columns, preFilteredColumns, isDateOverrideToTimestamp(), getIdentifierQuoteString()); } diff --git a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/postgresql/PostgresSqlStatementBuilder.java b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/postgresql/PostgresDmlStatement.java similarity index 90% rename from symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/postgresql/PostgresSqlStatementBuilder.java rename to symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/postgresql/PostgresDmlStatement.java index 29beb29470..69d8f4aeb2 100644 --- a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/postgresql/PostgresSqlStatementBuilder.java +++ b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/db/postgresql/PostgresDmlStatement.java @@ -21,15 +21,15 @@ package org.jumpmind.symmetric.db.postgresql; import org.apache.commons.lang.ArrayUtils; +import org.jumpmind.symmetric.db.DmlStatement; import org.jumpmind.symmetric.ddl.model.Column; -import org.jumpmind.symmetric.load.StatementBuilder; -public class PostgresSqlStatementBuilder extends StatementBuilder { +public class PostgresDmlStatement extends DmlStatement { - public PostgresSqlStatementBuilder(DmlType type, String tableName, Column[] keys, + public PostgresDmlStatement(DmlType type, String catalogName, String schemaName, String tableName, Column[] keys, Column[] columns, Column[] preFilteredColumns, boolean isDateOverrideToTimestamp, String identifierQuoteString) { - super(type, tableName, keys, columns, preFilteredColumns, isDateOverrideToTimestamp, + super(type, catalogName, schemaName, tableName, keys, columns, preFilteredColumns, isDateOverrideToTimestamp, identifierQuoteString); } diff --git a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/load/IColumnFilter.java b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/load/IColumnFilter.java index f046c7f3c9..d6b351f245 100644 --- a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/load/IColumnFilter.java +++ b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/load/IColumnFilter.java @@ -20,9 +20,9 @@ package org.jumpmind.symmetric.load; +import org.jumpmind.symmetric.db.DmlStatement.DmlType; import org.jumpmind.symmetric.ddl.model.Table; import org.jumpmind.symmetric.ext.IExtensionPoint; -import org.jumpmind.symmetric.load.StatementBuilder.DmlType; /** * This filter will be deprecated in the future. Please use the data transformation diff --git a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/load/TableTemplate.java b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/load/TableTemplate.java index db457631dc..edddfffb4b 100644 --- a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/load/TableTemplate.java +++ b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/load/TableTemplate.java @@ -28,10 +28,11 @@ import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; +import org.jumpmind.symmetric.db.DmlStatement; import org.jumpmind.symmetric.db.IDbDialect; +import org.jumpmind.symmetric.db.DmlStatement.DmlType; import org.jumpmind.symmetric.ddl.model.Column; import org.jumpmind.symmetric.ddl.model.Table; -import org.jumpmind.symmetric.load.StatementBuilder.DmlType; import org.jumpmind.symmetric.util.ArgTypePreparedStatementSetter; import org.springframework.jdbc.core.JdbcTemplate; @@ -66,7 +67,7 @@ public class TableTemplate { private Map allMetaData; - private HashMap statementMap; + private HashMap statementMap; private List columnFilters = new ArrayList(); @@ -88,7 +89,7 @@ public TableTemplate(JdbcTemplate jdbcTemplate, IDbDialect dbDialect, String tab public void resetMetaData(boolean useCache) { table = dbDialect.getTable(catalog, schema, tableName, useCache); allMetaData = new HashMap(); - statementMap = new HashMap(); + statementMap = new HashMap(); if (table != null) { for (Column column : table.getColumns()) { @@ -118,12 +119,12 @@ final public boolean isIgnoreThisTable() { } public int insert(IDataLoaderContext ctx, String[] columnValues, String[] keyValues) { - StatementBuilder st = getStatementBuilder(ctx, DmlType.INSERT, columnNames); + DmlStatement st = getStatementBuilder(ctx, DmlType.INSERT, columnNames); return execute(ctx, st, columnValues, keyValues); } public int update(IDataLoaderContext ctx, String[] columnValues, String[] keyValues) { - StatementBuilder st = null; + DmlStatement st = null; ArrayList changedColumnNameList = new ArrayList(); ArrayList changedColumnValueList = new ArrayList(); ArrayList changedColumnMetaList = new ArrayList(); @@ -190,7 +191,7 @@ protected String getKeyValue(IDataLoaderContext ctx, Column column, String[] key } public int delete(IDataLoaderContext ctx, String[] keyValues) { - StatementBuilder st = getStatementBuilder(ctx, DmlType.DELETE, columnNames); + DmlStatement st = getStatementBuilder(ctx, DmlType.DELETE, columnNames); if (keyValues == null || keyValues.length == 0) { keyValues = oldData; } @@ -198,7 +199,7 @@ public int delete(IDataLoaderContext ctx, String[] keyValues) { } public int count(IDataLoaderContext ctx, String[] keyValues) { - StatementBuilder st = getStatementBuilder(ctx, DmlType.COUNT, columnNames); + DmlStatement st = getStatementBuilder(ctx, DmlType.COUNT, columnNames); if (columnFilters != null) { for (IColumnFilter columnFilter : columnFilters) { keyValues = columnFilter.filterColumnsValues(ctx, st.getDmlType(), getTable(), @@ -208,26 +209,11 @@ public int count(IDataLoaderContext ctx, String[] keyValues) { Object[] objectValues = dbDialect.getObjectValues(ctx.getBinaryEncoding(), keyValues, st.getKeys()); return jdbcTemplate.queryForInt(st.getSql(), objectValues, st.getTypes()); - } - public String getFullyQualifiedTableName() { - return getFullyQualifiedTableName(false); - } - - public String getFullyQualifiedTableName(boolean preventQuotes) { - String quote = !preventQuotes ? dbDialect.getIdentifierQuoteString() : ""; - String tableName = quote + (table != null ? table.getName() : this.tableName) + quote; - if (!StringUtils.isBlank(schema)) { - tableName = schema + "." + tableName; - } - if (!StringUtils.isBlank(catalog)) { - tableName = catalog + "." + tableName; - } - return tableName; - } + } - final private StatementBuilder getStatementBuilder(IDataLoaderContext ctx, DmlType type, + final private DmlStatement getStatementBuilder(IDataLoaderContext ctx, DmlType type, String[] preFilteredColumnNames) { - StatementBuilder st = statementMap.get(type); + DmlStatement st = statementMap.get(type); if (st == null) { this.filteredColumnNames = preFilteredColumnNames; if (columnFilters != null) { @@ -235,16 +221,15 @@ final private StatementBuilder getStatementBuilder(IDataLoaderContext ctx, DmlTy this.filteredColumnNames = columnFilter.filterColumnsNames(ctx, type, getTable(), this.filteredColumnNames); } - } - - String tableName = getFullyQualifiedTableName(); + } String[] lookupColumnNames = keyNames; if (type == DmlType.UPDATE || type == DmlType.DELETE) { lookupColumnNames = getLookupValues(); } - - st = dbDialect.createStatementBuilder(type, tableName, getColumnMetaData(lookupColumnNames), + + String tableName = table != null ? table.getName() : this.tableName; + st = dbDialect.createStatementBuilder(type, catalog, schema, tableName, getColumnMetaData(lookupColumnNames), getColumnMetaData(this.filteredColumnNames), getColumnMetaData(preFilteredColumnNames)); @@ -271,7 +256,7 @@ public Object[] getObjectKeyValues(IDataLoaderContext ctx, String[] values) { return dbDialect.getObjectValues(ctx.getBinaryEncoding(), values, getColumnMetaData(keyNames)); } - final private int execute(IDataLoaderContext ctx, StatementBuilder st, String[] columnValues, String[] keyValues) { + final private int execute(IDataLoaderContext ctx, DmlStatement st, String[] columnValues, String[] keyValues) { if (columnFilters != null) { for (IColumnFilter columnFilter : columnFilters) { columnValues = columnFilter.filterColumnsValues(ctx, st.getDmlType(), getTable(), diff --git a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/load/csv/CsvLoader.java b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/load/csv/CsvLoader.java index b8d17976b7..649fb4a30c 100644 --- a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/load/csv/CsvLoader.java +++ b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/load/csv/CsvLoader.java @@ -149,10 +149,9 @@ public void skip() throws IOException { private void logTableIgnored() { TableTemplate tableTemplate = context.getTableTemplate(); if (tableTemplate != null) { - String tableName = tableTemplate.getFullyQualifiedTableName(); - if (!missingTables.contains(tableName)) { - log.warn("LoaderTableMissing", tableName); - missingTables.add(tableName); + if (!missingTables.contains(tableTemplate.getTableName())) { + log.warn("LoaderTableMissing", tableTemplate.getTableName()); + missingTables.add(tableTemplate.getTableName()); } } } diff --git a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/AbstractTransformer.java b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/AbstractTransformer.java index c8438d148b..a79da25e41 100644 --- a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/AbstractTransformer.java +++ b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/AbstractTransformer.java @@ -9,8 +9,8 @@ import org.jumpmind.symmetric.common.logging.ILog; import org.jumpmind.symmetric.common.logging.LogFactory; import org.jumpmind.symmetric.db.IDbDialect; +import org.jumpmind.symmetric.db.DmlStatement.DmlType; import org.jumpmind.symmetric.ext.ICacheContext; -import org.jumpmind.symmetric.load.StatementBuilder.DmlType; import org.jumpmind.symmetric.model.NodeGroupLink; import org.jumpmind.symmetric.service.IParameterService; import org.jumpmind.symmetric.util.AppUtils; diff --git a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformDataExtractor.java b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformDataExtractor.java index faf30fe3e1..4bbbaa5540 100644 --- a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformDataExtractor.java +++ b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformDataExtractor.java @@ -23,9 +23,9 @@ import java.util.ArrayList; import java.util.List; +import org.jumpmind.symmetric.db.DmlStatement.DmlType; import org.jumpmind.symmetric.ext.ICacheContext; import org.jumpmind.symmetric.extract.DataExtractorContext; -import org.jumpmind.symmetric.load.StatementBuilder.DmlType; import org.jumpmind.symmetric.model.Data; import org.jumpmind.symmetric.model.DataEventType; import org.jumpmind.symmetric.model.Router; diff --git a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformDataLoader.java b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformDataLoader.java index 687d898d06..e3c0ee911f 100644 --- a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformDataLoader.java +++ b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformDataLoader.java @@ -25,12 +25,12 @@ import org.apache.commons.lang.ArrayUtils; import org.jumpmind.symmetric.SymmetricException; import org.jumpmind.symmetric.common.ParameterConstants; +import org.jumpmind.symmetric.db.DmlStatement.DmlType; import org.jumpmind.symmetric.ddl.model.Table; import org.jumpmind.symmetric.ext.IBuiltInExtensionPoint; import org.jumpmind.symmetric.load.IDataLoaderContext; import org.jumpmind.symmetric.load.IDataLoaderFilter; import org.jumpmind.symmetric.load.IMissingTableHandler; -import org.jumpmind.symmetric.load.StatementBuilder.DmlType; import org.jumpmind.symmetric.load.TableTemplate; public class TransformDataLoader extends AbstractTransformer implements IBuiltInExtensionPoint, @@ -249,7 +249,7 @@ protected void apply(IDataLoaderContext context, public boolean isHandlingMissingTable(IDataLoaderContext context) { List transformationsToPerform = findTablesToTransform( context.getNodeGroupLink(), - context.getTableTemplate().getFullyQualifiedTableName(true)); + Table.getFullyQualifiedTableName(context.getCatalogName(), context.getSchemaName(), context.getTableName(), "")); return transformationsToPerform != null && transformationsToPerform.size() > 0; } diff --git a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformTable.java b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformTable.java index bc18b6cc89..7371fbebc3 100644 --- a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformTable.java +++ b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformTable.java @@ -24,13 +24,13 @@ public class TransformTable { protected int transformOrder = 0; public String getFullyQualifiedSourceTableName() { - return Table.getFullyQualifiedTableName(sourceTableName, sourceSchemaName, - sourceCatalogName); + return Table.getFullyQualifiedTableName(sourceCatalogName, sourceSchemaName, + sourceTableName); } - + public String getFullyQualifiedTargetTableName() { - return Table.getFullyQualifiedTableName(targetTableName, targetSchemaName, - targetCatalogName); + return Table.getFullyQualifiedTableName(sourceCatalogName, sourceSchemaName, + sourceTableName); } public String getTransformId() { @@ -92,7 +92,7 @@ public void setTargetTableName(String targetTableName) { public void setTransformPoint(TransformPoint transformPoint) { this.transformPoint = transformPoint; } - + public TransformPoint getTransformPoint() { return transformPoint; } @@ -122,7 +122,7 @@ public List getTransformColumnFor(String columnName) { for (TransformColumn column : transformColumns) { if (column.getSourceColumnName().equals(columnName)) { columns.add(column); - } + } } return columns; } @@ -163,15 +163,15 @@ public void setUpdateFirst(boolean updateFirst) { public boolean isUpdateFirst() { return updateFirst; } - + public void setNodeGroupLink(NodeGroupLink nodeGroupLink) { this.nodeGroupLink = nodeGroupLink; } - + public NodeGroupLink getNodeGroupLink() { return nodeGroupLink; } - + @Override public int hashCode() { if (transformId != null) { @@ -180,7 +180,7 @@ public int hashCode() { return super.hashCode(); } } - + @Override public boolean equals(Object obj) { if (transformId != null) { @@ -200,6 +200,6 @@ public String toString() { return transformId; } else { return super.toString(); - } + } } } diff --git a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformedData.java b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformedData.java index 54af06330e..c5db2482af 100644 --- a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformedData.java +++ b/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/transform/TransformedData.java @@ -6,7 +6,7 @@ import java.util.List; import java.util.Map; -import org.jumpmind.symmetric.load.StatementBuilder.DmlType; +import org.jumpmind.symmetric.db.DmlStatement.DmlType; import org.jumpmind.symmetric.transform.TransformColumn.IncludeOnType; public class TransformedData implements Cloneable { diff --git a/symmetric/symmetric-ddl/pom.xml b/symmetric/symmetric-ddl/pom.xml index 770bee022c..8a84254f6e 100644 --- a/symmetric/symmetric-ddl/pom.xml +++ b/symmetric/symmetric-ddl/pom.xml @@ -1,7 +1,7 @@ 4.0.0 org.jumpmind.symmetric - symmetric-ddl + symmetric-db jar 2.6.0-SNAPSHOT ddl @@ -51,6 +51,10 @@ + + org.jumpmind.symmetric + symmetric-util + junit junit diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/AbstractSqlTemplate.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/AbstractSqlTemplate.java new file mode 100644 index 0000000000..56e2aee4e8 --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/AbstractSqlTemplate.java @@ -0,0 +1,120 @@ +package org.jumpmind.symmetric.db; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jumpmind.symmetric.db.DmlStatement.DmlType; +import org.jumpmind.symmetric.ddl.model.Table; + +abstract public class AbstractSqlTemplate implements ISqlTemplate { + + protected boolean dateOverrideToTimestamp; + + protected String identifierQuoteString; + + public int queryForInt(String sql) { + return queryForObject(sql, Number.class, (Object[]) null).intValue(); + } + + public ISqlReadCursor queryForCursor(Query query, ISqlRowMapper mapper) { + return this.queryForCursor(query.getSql(), mapper, query.getArgs(), query.getArgTypes()); + } + + public ISqlReadCursor queryForCursor(String sql, ISqlRowMapper mapper) { + return this.queryForCursor(sql, mapper, null, null); + } + + public List query(String sql) { + return query(sql, (Object[]) null, (int[]) null); + } + + public List query(String sql, ISqlRowMapper mapper, Object... args) { + return query(sql, mapper, args, null); + } + + public List query(Query query, ISqlRowMapper mapper) { + + return query(query.getSql(), mapper, query.getArgs(), query.getArgTypes()); + } + + @SuppressWarnings("unchecked") + public Map query(String sql, String keyCol, String valueCol, Object[] args, + int[] types) { + List rows = query(sql, args, types); + Map map = new HashMap(rows.size()); + for (Row row : rows) { + map.put((T) row.get(keyCol), (W) row.get(valueCol)); + } + return map; + } + + public List query(String sql, Object[] args, int[] types) { + return query(sql, new ISqlRowMapper() { + public Row mapRow(Row row) { + return row; + } + }, args, types); + } + + public List query(String sql, ISqlRowMapper mapper) { + return query(sql, mapper, null, null); + } + + public List query(String sql, ISqlRowMapper mapper, Object[] args, int[] types) { + ISqlReadCursor cursor = queryForCursor(sql, mapper, args, types); + try { + T next = null; + List list = new ArrayList(); + do { + next = cursor.next(); + if (next != null) { + list.add(next); + } + } while (next != null); + return list; + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + public int update(String sql) { + return update(sql, null, null); + } + + public int update(String... sql) { + return update(true, true, -1, sql); + } + + public int update(Table table, Map params) { + return update(DmlType.UPDATE, table, params); + } + + public int insert(Table table, Map params) { + return update(DmlType.INSERT, table, params); + } + + public int delete(Table table, Map params) { + return update(DmlType.DELETE, table, params); + } + + public void save(Table table, Map params) { + if (update(table, params) == 0) { + insert(table, params); + } + } + + protected int update(DmlType type, Table table, Map params) { + DmlStatement updateStmt = new DmlStatement(type, table.getCatalog(), table.getSchema(), + table.getName(), table.getPrimaryKeyColumns(), table.getColumns(), null, + dateOverrideToTimestamp, identifierQuoteString); + String sql = updateStmt.getSql(); + int[] types = updateStmt.getTypes(); + Object[] values = updateStmt.buildArgsFrom(params); + return update(sql, values, types); + } + +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/ColumnNotFoundException.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/ColumnNotFoundException.java new file mode 100644 index 0000000000..c9c6080f2f --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/ColumnNotFoundException.java @@ -0,0 +1,10 @@ +package org.jumpmind.symmetric.db; + +public class ColumnNotFoundException extends SqlException { + + private static final long serialVersionUID = 1L; + + public ColumnNotFoundException(String columnName) { + super(columnName); + } +} diff --git a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/load/StatementBuilder.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/DmlStatement.java similarity index 80% rename from symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/load/StatementBuilder.java rename to symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/DmlStatement.java index 5f95415f60..ae2b80ba7c 100644 --- a/symmetric/symmetric-core/src/main/java/org/jumpmind/symmetric/load/StatementBuilder.java +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/DmlStatement.java @@ -19,22 +19,24 @@ * under the License. */ -package org.jumpmind.symmetric.load; +package org.jumpmind.symmetric.db; import java.sql.Types; import java.util.ArrayList; import java.util.HashSet; +import java.util.Map; import java.util.Set; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.NotImplementedException; import org.jumpmind.symmetric.ddl.model.Column; +import org.jumpmind.symmetric.ddl.model.Table; /** * Builds a SQL DML statement */ -public class StatementBuilder { +public class DmlStatement { public enum DmlType { INSERT, UPDATE, DELETE, COUNT @@ -54,20 +56,20 @@ public enum DmlType { protected Column[] preFilteredColumns; - public StatementBuilder(DmlType type, String tableName, Column[] keys, Column[] columns, + public DmlStatement(DmlType type, String catalogName, String schemaName, String tableName, Column[] keys, Column[] columns, Column[] preFilteredColumns, boolean isDateOverrideToTimestamp, String identifierQuoteString) { this.keys = keys; this.columns = columns; this.preFilteredColumns = preFilteredColumns; quote = identifierQuoteString == null ? "" : identifierQuoteString; if (type == DmlType.INSERT) { - sql = buildInsertSql(tableName, keys, columns); + sql = buildInsertSql(Table.getFullyQualifiedTableName(catalogName, schemaName, tableName, identifierQuoteString), keys, columns); } else if (type == DmlType.UPDATE) { - sql = buildUpdateSql(tableName, keys, columns); + sql = buildUpdateSql(Table.getFullyQualifiedTableName(catalogName, schemaName, tableName, identifierQuoteString), keys, columns); } else if (type == DmlType.DELETE) { - sql = buildDeleteSql(tableName, keys); + sql = buildDeleteSql(Table.getFullyQualifiedTableName(catalogName, schemaName, tableName, identifierQuoteString), keys); } else if (type == DmlType.COUNT) { - sql = buildCountSql(tableName, keys); + sql = buildCountSql(Table.getFullyQualifiedTableName(catalogName, schemaName, tableName, identifierQuoteString), keys); } else { throw new NotImplementedException("Unimplemented SQL type: " + type); } @@ -279,5 +281,38 @@ public String[] getValueArray(String[] columnValues, String[] keyValues) { return keyValues; } return null; - } + } + + public Object[] buildArgsFrom(Map params) { + Object[] args = null; + if (params != null) { + int index = 0; + switch (dmlType) { + case INSERT: + args = new Object[columns.length]; + for (Column column : columns) { + args[index++] = params.get(column.getName()); + } + break; + case UPDATE: + args = new Object[columns.length + keys.length]; + for (Column column : columns) { + args[index++] = params.get(column.getName()); + } + for (Column column : keys) { + args[index++] = params.get(column.getName()); + } + break; + case DELETE: + args = new Object[keys.length]; + for (Column column : keys) { + args[index++] = params.get(column.getName()); + } + break; + default: + throw new UnsupportedOperationException(); + } + } + return args; + } } \ No newline at end of file diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/ISqlReadCursor.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/ISqlReadCursor.java new file mode 100644 index 0000000000..9890dc17d7 --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/ISqlReadCursor.java @@ -0,0 +1,9 @@ +package org.jumpmind.symmetric.db; + +public interface ISqlReadCursor { + + public T next(); + + public void close(); + +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/ISqlRowMapper.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/ISqlRowMapper.java new file mode 100644 index 0000000000..ba0b573de0 --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/ISqlRowMapper.java @@ -0,0 +1,6 @@ +package org.jumpmind.symmetric.db; + + +public interface ISqlRowMapper { + public T mapRow(Row row); +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/ISqlTemplate.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/ISqlTemplate.java new file mode 100644 index 0000000000..cf152ae8b3 --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/ISqlTemplate.java @@ -0,0 +1,67 @@ +package org.jumpmind.symmetric.db; + +import java.util.List; +import java.util.Map; + +import org.jumpmind.symmetric.ddl.model.Table; + + +/** + * This interface insulates the application from the data connection technology. + */ +public interface ISqlTemplate { + + public T queryForObject(String sql, Class clazz, Object... params); + + public int queryForInt(String sql); + + public ISqlReadCursor queryForCursor(Query query, ISqlRowMapper mapper) ; + + public ISqlReadCursor queryForCursor(String sql, ISqlRowMapper mapper, + Object[] params, int[] types); + + public ISqlReadCursor queryForCursor(String sql, ISqlRowMapper mapper); + + public List query(String sql); + + public List query(String sql, Object[] params, int[] types); + + public List query(String sql, ISqlRowMapper mapper); + + public List query(String sql, ISqlRowMapper mapper, Object... params); + + public List query(String sql, ISqlRowMapper mapper, Object[] params, int[] types); + + public List query(Query query, ISqlRowMapper mapper); + + public Map query(String sql, String keyCol, String valueCol, Object[] params, int[] types); + + public int update(String sql); + + public int update(String... sql); + + public int update(boolean autoCommit, boolean failOnError, int commitRate, String... sql); + + public int update(String sql, Object[] values, int[] types); + + public int update(Table table, Map params); + + public int insert(Table table, Map params); + + public int delete(Table table, Map params); + + public void save(Table table, Map params); + + public void testConnection(); + + public SqlException translate(Exception ex); + + public ISqlTransaction startSqlTransaction(); + + public int getDatabaseMajorVersion(); + + public int getDatabaseMinorVersion(); + + public String getDatabaseProductName(); + +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/ISqlTransaction.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/ISqlTransaction.java new file mode 100644 index 0000000000..1d4ebe89e0 --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/ISqlTransaction.java @@ -0,0 +1,36 @@ +package org.jumpmind.symmetric.db; + +import java.util.List; + +public interface ISqlTransaction { + + public boolean isInBatchMode(); + + public void setInBatchMode(boolean batchMode); + + public void setNumberOfRowsBeforeBatchFlush(int numberOfRowsBeforeBatchFlush); + + public int getNumberOfRowsBeforeBatchFlush(); + + public T queryForObject(final String sql, Class clazz, final Object... args); + + public void commit(); + + public void rollback(); + + public void close(); + + /** + * Each time the SQL changes it needs to be submitted for preparation + */ + public void prepare(String sql); + + public int update(T marker); + + public int update(T marker, Object[] values, int[] types); + + public int flush(); + + public List getUnflushedMarkers(boolean clear); + +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/Query.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/Query.java new file mode 100644 index 0000000000..83c7bae390 --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/Query.java @@ -0,0 +1,261 @@ +package org.jumpmind.symmetric.db; + +import java.util.ArrayList; +import java.util.List; + +import org.jumpmind.symmetric.ddl.model.Column; +import org.jumpmind.symmetric.ddl.model.Table; + +public class Query { + + protected Table[] tables; + + protected List args; + + protected List argTypes; + + protected StringBuilder sql; + + protected String quoteString; + + static public Query create(String quoteString, int expectedNumberOfArgs, Table... tables) { + return new Query(quoteString, expectedNumberOfArgs, tables); + } + + static public Query create(String quoteString, Table... tables) { + return new Query(quoteString, 0, tables); + } + + public Query(String quoteString, int expectedNumberOfArgs, String... tables) { + this(quoteString, expectedNumberOfArgs, buildTables(tables)); + } + + public Query(String quoteString, int expectedNumberOfArgs, Table... tables) { + this.quoteString = quoteString; + this.tables = tables; + this.args = new ArrayList(expectedNumberOfArgs); + this.argTypes = new ArrayList(expectedNumberOfArgs); + this.sql = select(quoteString, tables); + } + + public String getSql() { + return sql.toString().trim(); + } + + public Object[] getArgs() { + return args.toArray(new Object[args.size()]); + } + + public int[] getArgTypes() { + int[] array = null; + if (argTypes.size() > 0 && argTypes.size() == args.size()) { + array = new int[argTypes.size()]; + for (int i = 0; i < argTypes.size(); i++) { + Integer type = argTypes.get(i); + if (type != null) { + array[i] = type; + } + } + + } + return array; + } + + public Query where() { + sql.append("where "); + return this; + } + + public Query where(String column, String condition, Object value) { + return where(new Column(column), condition, value); + } + + public Query where(Column column, String condition, Object value) { + return where().append(column, condition, value); + } + + protected Query append(Column column, String condition, Object value) { + String tableAlias = getTableAlias(findTableIndexWith(column, tables), tables); + sql.append(tableAlias); + sql.append("."); + sql.append(column.getName()); + sql.append(condition); + if (value == null) { + sql.append(" is null "); + } else { + sql.append("? "); + args.add(value); + if (column.getTypeCode() != Integer.MAX_VALUE) { + argTypes.add(column.getTypeCode()); + } + } + return this; + } + + public Query append(String value) { + sql.append(value); + sql.append(" "); + return this; + } + + public Query and(String column, String condition, Object value) { + return and(new Column(column), condition, value); + } + + public Query and(Column column, String condition, Object value) { + return append("and").append(column, condition, value); + } + + public Query or(String column, String condition, Object value) { + return or(new Column(column), condition, value); + } + + public Query or(Column column, String condition, Object value) { + return append("or").append(column, condition, value); + } + + public Query startGroup() { + sql.append("("); + return this; + } + + public Query endGroup() { + sql.append(")"); + return this; + } + + @Override + public String toString() { + return getSql(); + } + + protected static StringBuilder select(String quoteString, Table[] tables) { + if (tables != null && tables.length > 0) { + StringBuilder sql = new StringBuilder("select "); + if (hasColumns(tables)) { + addColumnList(sql, tables); + addTables(sql, quoteString, tables); + } else { + if (tables.length == 1) { + sql.append("* from "); + sql.append(tables[0].getFullyQualifiedTableName(quoteString)); + } else { + throw new IllegalStateException( + "Cannot join tables if columns are not specified"); + } + } + return sql; + } else { + throw new IllegalStateException("Need a list of tables to build a select statement"); + } + } + + protected static void addColumnList(StringBuilder sql, Table... tables) { + for (int i = 0; i < tables.length; i++) { + Table table = tables[i]; + String tableAlias = getTableAlias(i, tables); + Column[] columns = table.getColumns(); + int columnIndex = 0; + for (Column column : columns) { + sql.append(tableAlias); + sql.append("."); + sql.append(column.getName()); + sql.append((columns.length > 1 && ++columnIndex < columns.length) || i+1 < tables.length ? ", " : " "); + } + } + } + + protected static String getTableAlias(int index, Table... tables) { + String tableAlias = "t"; + if (tables.length > 1 && index >= 0) { + tableAlias += (index + 1); + } + return tableAlias; + } + + protected static void addTables(StringBuilder sql, String quoteString, Table... tables) { + sql.append("from "); + for (int i = 0; i < tables.length; i++) { + Table lastTable = i > 0 ? tables[i - 1] : null; + Table table = tables[i]; + String tableAlias = getTableAlias(i, tables); + if (lastTable != null) { + sql.append("inner join "); + sql.append(table.getFullyQualifiedTableName(quoteString)); + sql.append(" "); + sql.append(tableAlias); + sql.append(" on "); + String lastTableAlias = getTableAlias(i - 1, tables); + List columns = autoBuildJoins(lastTable, table); + int columnIndex = 0; + for (Column column : columns) { + sql.append(lastTableAlias); + sql.append("."); + sql.append(column.getName()); + sql.append("="); + sql.append(tableAlias); + sql.append("."); + sql.append(column.getName()); + sql.append(columns.size() > 1 && ++columnIndex < columns.size() ? " AND " : " "); + } + } else { + sql.append(table.getFullyQualifiedTableName(quoteString)); + sql.append(" "); + sql.append(tableAlias); + sql.append(" "); + } + } + } + + protected static List autoBuildJoins(Table t1, Table t2) { + List columns = new ArrayList(); + Column[] t1Columns = t1.getColumns(); + for (Column column1 : t1Columns) { + Column[] t2Columns = t2.getColumns(); + for (Column column2 : t2Columns) { + if (column1.getName().equals(column2.getName())) { + columns.add(column1); + } + } + } + return columns; + } + + protected static boolean hasColumns(Table... tables) { + boolean hasColumns = true; + for (Table table : tables) { + hasColumns &= table.getColumnCount() > 0; + } + return hasColumns; + } + + protected static Table[] buildTables(String... tables) { + if (tables != null) { + Table[] array = new Table[tables.length]; + for (int i = 0; i < tables.length; i++) { + array[i] = new Table(tables[i]); + } + return array; + } else { + return null; + } + } + + protected static int findTableIndexWith(Column column, Table[] tables) { + int matchOnNameIndex = -1; + for (int i = 0; i < tables.length; i++) { + Table table = tables[i]; + Column[] columns = table.getColumns(); + for (Column column2 : columns) { + if (column2.equals(column)) { + return i; + } else if (matchOnNameIndex < 0 && column2.getName().equals(column.getName())) { + matchOnNameIndex = i; + } + } + } + return matchOnNameIndex; + + } + +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/Row.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/Row.java new file mode 100644 index 0000000000..e74d394082 --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/Row.java @@ -0,0 +1,109 @@ +package org.jumpmind.symmetric.db; + +import java.text.ParseException; +import java.util.Date; +import java.util.HashMap; + +import org.apache.commons.lang.time.DateUtils; + +public class Row extends HashMap { + + private static final long serialVersionUID = 1L; + + public Row(int numberOfColumns) { + super(numberOfColumns); + } + + public Row(String columnName, Object value) { + super(1); + put(columnName, value); + } + + + public String stringValue() { + Object obj = this.values().iterator().next(); + if (obj != null) { + return obj.toString(); + } else { + return null; + } + } + + public String getString(String columnName) { + Object obj = this.get(columnName); + if (obj != null) { + return obj.toString(); + } else { + checkForColumn(columnName); + return null; + } + } + + public int getInt(String columnName) { + Object obj = this.get(columnName); + if (obj instanceof Number) { + return ((Number)obj).intValue(); + } else if (obj instanceof String){ + return Integer.parseInt(obj.toString()); + } else { + checkForColumn(columnName); + return 0; + } + } + + public long getLong(String columnName) { + Object obj = this.get(columnName); + if (obj instanceof Number) { + return ((Number)obj).longValue(); + } else if (obj instanceof String){ + return Long.parseLong(obj.toString()); + } else { + checkForColumn(columnName); + return 0; + } + } + + public boolean getBoolean(String columnName) { + Object obj = this.get(columnName); + if (obj instanceof Number) { + int value = ((Number)obj).intValue(); + return value > 0 ? true : false; + } else if (obj instanceof Boolean){ + return (Boolean)obj; + } else if (obj instanceof String) { + return Boolean.parseBoolean((String)obj); + } else { + checkForColumn(columnName); + return false; + } + } + + public Date getDateTime(String columnName) { + Object obj = this.get(columnName); + if (obj instanceof Number) { + long value = ((Number)obj).longValue(); + return new Date(value); + } else if (obj instanceof Date) { + return (Date)obj; + } else if (obj instanceof String) { + return getDate((String)obj, SqlConstants.TIMESTAMP_PATTERNS); + } else { + checkForColumn(columnName); + return null; + } + } + + protected void checkForColumn(String columnName) { + if (!containsKey(columnName)) { + throw new ColumnNotFoundException(columnName); + } + } + + final private java.util.Date getDate(String value, String[] pattern) { + try { + return DateUtils.parseDate(value, pattern); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/SqlConstants.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/SqlConstants.java new file mode 100644 index 0000000000..7b540e904d --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/SqlConstants.java @@ -0,0 +1,24 @@ +package org.jumpmind.symmetric.db; + +import org.apache.commons.lang.time.FastDateFormat; +import org.jumpmind.symmetric.db.mapper.StringMapper; + +abstract public class SqlConstants { + + public static final String[] TIMESTAMP_PATTERNS = { "yyyy-MM-dd HH:mm:ss.S", + "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM-dd" }; + + public static final String[] TIME_PATTERNS = { "HH:mm:ss.S", "HH:mm:ss", + "yyyy-MM-dd HH:mm:ss.S", "yyyy-MM-dd HH:mm:ss" }; + + public static final FastDateFormat JDBC_TIMESTAMP_FORMATTER = FastDateFormat + .getInstance("yyyy-MM-dd hh:mm:ss.SSS"); + + public static final String ALWAYS_TRUE_CONDITION = "1=1"; + + public static final int DEFAULT_QUERY_TIMEOUT_SECONDS = 300; + + public static final int DEFAULT_STREAMING_FETCH_SIZE = 1000; + + public static final StringMapper STRING_MAPPER = new StringMapper(); +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/SqlException.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/SqlException.java new file mode 100644 index 0000000000..5db0e20511 --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/SqlException.java @@ -0,0 +1,23 @@ +package org.jumpmind.symmetric.db; + +public class SqlException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public SqlException() { + super(); + } + + public SqlException(String message, Throwable cause) { + super(message, cause); + } + + public SqlException(String message) { + super(message); + } + + public SqlException(Throwable cause) { + super(cause); + } + +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/IConnectionCallback.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/IConnectionCallback.java new file mode 100644 index 0000000000..eecb15062a --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/IConnectionCallback.java @@ -0,0 +1,10 @@ +package org.jumpmind.symmetric.db.jdbc; + +import java.sql.Connection; +import java.sql.SQLException; + +public interface IConnectionCallback { + + public T execute(Connection con) throws SQLException; + +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/ILobHandler.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/ILobHandler.java new file mode 100644 index 0000000000..33644f9474 --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/ILobHandler.java @@ -0,0 +1,44 @@ +package org.jumpmind.symmetric.db.jdbc; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public interface ILobHandler { + + /** + * Set the given content as bytes on the given statement, using the given + * parameter index. Might simply invoke + * PreparedStatement.setBytes or create a Blob instance for it, + * depending on the database and driver. + * + * @param ps + * the PreparedStatement to the set the content on + * @param paramIndex + * the parameter index to use + * @param content + * the content as byte array, or null for SQL NULL + * @throws SQLException + * if thrown by JDBC methods + * @see java.sql.PreparedStatement#setBytes + */ + void setBlobAsBytes(PreparedStatement ps, int paramIndex, byte[] content) throws SQLException; + + /** + * Set the given content as String on the given statement, using the given + * parameter index. Might simply invoke + * PreparedStatement.setString or create a Clob instance for + * it, depending on the database and driver. + * + * @param ps + * the PreparedStatement to the set the content on + * @param paramIndex + * the parameter index to use + * @param content + * the content as String, or null for SQL NULL + * @throws SQLException + * if thrown by JDBC methods + * @see java.sql.PreparedStatement#setBytes + */ + void setClobAsString(PreparedStatement ps, int paramIndex, String content) throws SQLException; + +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/JdbcSqlReadCursor.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/JdbcSqlReadCursor.java new file mode 100644 index 0000000000..23461ee23e --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/JdbcSqlReadCursor.java @@ -0,0 +1,89 @@ +package org.jumpmind.symmetric.db.jdbc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; + +import org.jumpmind.symmetric.db.ISqlReadCursor; +import org.jumpmind.symmetric.db.ISqlRowMapper; +import org.jumpmind.symmetric.db.Row; + +public class JdbcSqlReadCursor implements ISqlReadCursor { + + protected Connection c; + + protected ResultSet rs; + + protected PreparedStatement st; + + protected boolean autoCommitFlag; + + protected ISqlRowMapper mapper; + + protected JdbcSqlTemplate sqlTemplate; + + protected int rowNumber; + + public JdbcSqlReadCursor() { + } + + public JdbcSqlReadCursor(JdbcSqlTemplate sqlTemplate, ISqlRowMapper mapper, String sql, + Object[] values, int[] types) { + this.sqlTemplate = sqlTemplate; + this.mapper = mapper; + try { + c = sqlTemplate.getDataSource().getConnection(); + autoCommitFlag = c.getAutoCommit(); + + if (sqlTemplate.isRequiresAutoCommitFalseToSetFetchSize()) { + c.setAutoCommit(false); + } + + st = c.prepareStatement(sql, java.sql.ResultSet.TYPE_FORWARD_ONLY, + java.sql.ResultSet.CONCUR_READ_ONLY); + st.setQueryTimeout(sqlTemplate.getQueryTimeout()); + if (values != null) { + StatementCreatorUtil.setValues(st, values, types, sqlTemplate.getLobHandler()); + } + st.setFetchSize(sqlTemplate.getFetchSize()); + rs = st.executeQuery(); + } catch (SQLException ex) { + throw sqlTemplate.translate(sql, ex); + } + } + + public T next() { + try { + if (rs.next()) { + Row row = getMapForRow(); + return mapper.mapRow(row); + } else { + return null; + } + } catch (SQLException ex) { + throw sqlTemplate.translate(ex); + } + } + + protected Row getMapForRow() throws SQLException { + ResultSetMetaData rsmd = rs.getMetaData(); + int columnCount = rsmd.getColumnCount(); + Row mapOfColValues = new Row(columnCount); + for (int i = 1; i <= columnCount; i++) { + String key = JdbcSqlTemplate.lookupColumnName(rsmd, i); + Object obj = JdbcSqlTemplate.getResultSetValue(rs, i); + mapOfColValues.put(key, obj); + } + return mapOfColValues; + } + + public void close() { + JdbcSqlTemplate.close(rs); + JdbcSqlTemplate.close(st); + JdbcSqlTemplate.close(autoCommitFlag, c); + + } + +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/JdbcSqlTemplate.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/JdbcSqlTemplate.java new file mode 100644 index 0000000000..ae24c29441 --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/JdbcSqlTemplate.java @@ -0,0 +1,374 @@ +package org.jumpmind.symmetric.db.jdbc; + +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; + +import javax.sql.DataSource; + +import org.jumpmind.symmetric.db.AbstractSqlTemplate; +import org.jumpmind.symmetric.db.ISqlReadCursor; +import org.jumpmind.symmetric.db.ISqlRowMapper; +import org.jumpmind.symmetric.db.ISqlTemplate; +import org.jumpmind.symmetric.db.ISqlTransaction; +import org.jumpmind.symmetric.db.SqlException; +import org.jumpmind.symmetric.util.Log; +import org.jumpmind.symmetric.util.LogFactory; +import org.jumpmind.symmetric.util.LogLevel; + +// TODO make sure connection timeouts are set properly +public class JdbcSqlTemplate extends AbstractSqlTemplate implements ISqlTemplate { + + static final Log log = LogFactory.getLog(JdbcSqlTemplate.class); + + protected DataSource dataSource; + + protected boolean requiresAutoCommitFalseToSetFetchSize = false; + + protected int queryTimeout; + + protected int fetchSize = 1000; + + public JdbcSqlTemplate() { + } + + public JdbcSqlTemplate(DataSource dataSource) { + this.dataSource = dataSource; + } + + public DataSource getDataSource() { + return dataSource; + } + + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } + + public boolean isRequiresAutoCommitFalseToSetFetchSize() { + return requiresAutoCommitFalseToSetFetchSize; + } + + public void setRequiresAutoCommitFalseToSetFetchSize( + boolean requiresAutoCommitFalseToSetFetchSize) { + this.requiresAutoCommitFalseToSetFetchSize = requiresAutoCommitFalseToSetFetchSize; + } + + public int getQueryTimeout() { + return queryTimeout; + } + + public void setQueryTimeout(int queryTimeout) { + this.queryTimeout = queryTimeout; + } + + public int getFetchSize() { + return fetchSize; + } + + public void setFetchSize(int fetchSize) { + this.fetchSize = fetchSize; + } + + public ILobHandler getLobHandler() { + return null; + } + + public ISqlReadCursor queryForCursor(String sql, ISqlRowMapper mapper, + Object[] values, int[] types) { + return new JdbcSqlReadCursor(this, mapper, sql, values, types); + } + + public T queryForObject(final String sql, Class clazz, final Object... args) { + return execute(new IConnectionCallback() { + @SuppressWarnings("unchecked") + public T execute(Connection con) throws SQLException { + T result = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + ps = con.prepareStatement(sql); + ps.setQueryTimeout(queryTimeout); + StatementCreatorUtil.setValues(ps, args); + rs = ps.executeQuery(); + if (rs.next()) { + result = (T) rs.getObject(1); + } + } finally { + close(rs); + close(ps); + } + return result; + } + }); + } + + public ISqlTransaction startSqlTransaction() { + return new JdbcSqlTransaction(this); + } + + public int update(final String sql, final Object[] values, final int[] types) { + return execute(new IConnectionCallback() { + public Integer execute(Connection con) throws SQLException { + if (values == null) { + Statement stmt = null; + try { + stmt = con.createStatement(); + return stmt.executeUpdate(sql); + } finally { + close(stmt); + } + } else { + PreparedStatement ps = null; + try { + ps = con.prepareStatement(sql); + ps.setQueryTimeout(queryTimeout); + StatementCreatorUtil.setValues(ps, values, types, getLobHandler()); + return ps.executeUpdate(); + } finally { + close(ps); + } + } + } + }); + } + + public int update(final boolean autoCommit, final boolean failOnError, final int commitRate, + final String... sql) { + return execute(new IConnectionCallback() { + public Integer execute(Connection con) throws SQLException { + int updateCount = 0; + boolean oldAutoCommitSetting = con.getAutoCommit(); + Statement stmt = null; + try { + con.setAutoCommit(autoCommit); + stmt = con.createStatement(); + int statementCount = 0; + for (String statement : sql) { + try { + updateCount += stmt.executeUpdate(statement); + statementCount++; + if (statementCount % commitRate == 0 && !autoCommit) { + con.commit(); + } + } catch (SQLException ex) { + if (!failOnError) { + log.log(LogLevel.WARN, "%s. Failed to execute: %s.", + ex.getMessage(), sql); + } else { + throw translate(statement, ex); + } + } + } + + if (!autoCommit) { + con.commit(); + } + return updateCount; + } catch (SQLException ex) { + if (!autoCommit) { + con.rollback(); + } + throw ex; + } finally { + close(stmt); + con.setAutoCommit(oldAutoCommitSetting); + } + } + }); + } + + public void testConnection() { + execute(new IConnectionCallback() { + public Boolean execute(Connection con) throws SQLException { + return true; + } + }); + } + + public T execute(IConnectionCallback callback) { + Connection c = null; + try { + c = dataSource.getConnection(); + return callback.execute(c); + } catch (SQLException ex) { + throw translate(ex); + } finally { + close(c); + } + } + + /** + * Determine the column name to use. The column name is determined based on + * a lookup using ResultSetMetaData. + *

+ * This method implementation takes into account recent clarifications + * expressed in the JDBC 4.0 specification: + *

+ * columnLabel - the label for the column specified with the SQL AS + * clause. If the SQL AS clause was not specified, then the label is the + * name of the column. + * + * @return the column name to use + * @param resultSetMetaData + * the current meta data to use + * @param columnIndex + * the index of the column for the look up + * @throws SQLException + * in case of lookup failure + */ + public static String lookupColumnName(ResultSetMetaData resultSetMetaData, int columnIndex) + throws SQLException { + String name = resultSetMetaData.getColumnLabel(columnIndex); + if (name == null || name.length() < 1) { + name = resultSetMetaData.getColumnName(columnIndex); + } + return name; + } + + /** + * Retrieve a JDBC column value from a ResultSet, using the most appropriate + * value type. The returned value should be a detached value object, not + * having any ties to the active ResultSet: in particular, it should not be + * a Blob or Clob object but rather a byte array respectively String + * representation. + *

+ * Uses the getObject(index) method, but includes additional + * "hacks" to get around Oracle 10g returning a non-standard object for its + * TIMESTAMP datatype and a java.sql.Date for DATE columns + * leaving out the time portion: These columns will explicitly be extracted + * as standard java.sql.Timestamp object. + * + * @param rs + * is the ResultSet holding the data + * @param index + * is the column index + * @return the value object + * @throws SQLException + * if thrown by the JDBC API + * @see java.sql.Blob + * @see java.sql.Clob + * @see java.sql.Timestamp + */ + public static Object getResultSetValue(ResultSet rs, int index) throws SQLException { + Object obj = rs.getObject(index); + String className = null; + if (obj != null) { + className = obj.getClass().getName(); + } + if (obj instanceof Blob) { + obj = rs.getBytes(index); + } else if (obj instanceof Clob) { + obj = rs.getString(index); + } else if (className != null + && ("oracle.sql.TIMESTAMP".equals(className) || "oracle.sql.TIMESTAMPTZ" + .equals(className))) { + obj = rs.getTimestamp(index); + } else if (className != null && className.startsWith("oracle.sql.DATE")) { + String metaDataClassName = rs.getMetaData().getColumnClassName(index); + if ("java.sql.Timestamp".equals(metaDataClassName) + || "oracle.sql.TIMESTAMP".equals(metaDataClassName)) { + obj = rs.getTimestamp(index); + } else { + obj = rs.getDate(index); + } + } else if (obj != null && obj instanceof java.sql.Date) { + if ("java.sql.Timestamp".equals(rs.getMetaData().getColumnClassName(index))) { + obj = rs.getTimestamp(index); + } + } + return obj; + } + + public static void close(ResultSet rs) { + try { + if (rs != null) { + rs.close(); + } + } catch (SQLException ex) { + } + } + + public static void close(PreparedStatement ps) { + try { + if (ps != null) { + ps.close(); + } + } catch (SQLException ex) { + } + } + + public static void close(Statement stmt) { + try { + if (stmt != null) { + stmt.close(); + } + } catch (SQLException ex) { + } + } + + public static void close(boolean autoCommitValue, Connection c) { + try { + if (c != null) { + c.setAutoCommit(autoCommitValue); + } + } catch (SQLException ex) { + } finally { + close(c); + } + } + + public static void close(Connection c) { + try { + if (c != null) { + c.close(); + } + } catch (SQLException ex) { + } + } + + public SqlException translate(Exception ex) { + return translate(ex.getMessage(), ex); + } + + public SqlException translate(String message, Exception ex) { + // TODO + // if (getDbDialect().isDataIntegrityException(ex)) { + // return new DataIntegrityViolationException(message, ex); + // } else + if (ex instanceof SqlException) { + return (SqlException) ex; + } else { + return new SqlException(message, ex); + } + } + + public int getDatabaseMajorVersion() { + return execute(new IConnectionCallback() { + public Integer execute(Connection con) throws SQLException { + return con.getMetaData().getDatabaseMajorVersion(); + } + }); + } + + public int getDatabaseMinorVersion() { + return execute(new IConnectionCallback() { + public Integer execute(Connection con) throws SQLException { + return con.getMetaData().getDatabaseMinorVersion(); + } + }); + } + + public String getDatabaseProductName() { + return execute(new IConnectionCallback() { + public String execute(Connection con) throws SQLException { + return con.getMetaData().getDatabaseProductName(); + } + }); + } + +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/JdbcSqlTransaction.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/JdbcSqlTransaction.java new file mode 100644 index 0000000000..3ab2d402aa --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/JdbcSqlTransaction.java @@ -0,0 +1,241 @@ +package org.jumpmind.symmetric.db.jdbc; + +import java.sql.BatchUpdateException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.jumpmind.symmetric.db.ISqlTransaction; + +/** + * TODO Support Oracle's non-standard way of batching + */ +public class JdbcSqlTransaction implements ISqlTransaction { + + protected boolean inBatchMode = true; + + protected Connection dbConnection; + + protected String psql; + + protected PreparedStatement pstmt; + + protected JdbcSqlTemplate sqlConnection; + + protected int numberOfRowsBeforeBatchFlush = 1000; + + protected boolean oldAutoCommitValue; + + protected List markers = new ArrayList(); + + public JdbcSqlTransaction(JdbcSqlTemplate sqlConnection) { + try { + this.sqlConnection = sqlConnection; + this.dbConnection = sqlConnection.getDataSource().getConnection(); + this.oldAutoCommitValue = this.dbConnection.getAutoCommit(); + this.dbConnection.setAutoCommit(false); + } catch (SQLException ex) { + throw sqlConnection.translate(ex); + } + } + + public void setNumberOfRowsBeforeBatchFlush(int numberOfRowsBeforeBatchFlush) { + this.numberOfRowsBeforeBatchFlush = numberOfRowsBeforeBatchFlush; + } + + public int getNumberOfRowsBeforeBatchFlush() { + return numberOfRowsBeforeBatchFlush; + } + + public void setInBatchMode(boolean useBatching) { + if (dbConnection != null) { + this.inBatchMode = useBatching; + } + } + + public boolean isInBatchMode() { + return inBatchMode; + } + + public void commit() { + if (dbConnection != null) { + try { + if (pstmt != null && inBatchMode) { + flush(); + } + dbConnection.commit(); + } catch (SQLException ex) { + throw sqlConnection.translate(ex); + } + } + } + + public void rollback() { + rollback(true); + } + + protected void rollback(boolean clearMarkers) { + if (dbConnection != null) { + try { + if (clearMarkers) { + markers.clear(); + } + dbConnection.rollback(); + } catch (SQLException ex) { + // do nothing + } + } + } + + public void close() { + if (dbConnection != null) { + JdbcSqlTemplate.close(pstmt); + try { + dbConnection.setAutoCommit(this.oldAutoCommitValue); + } catch (SQLException ex) { + // do nothing + } + try { + dbConnection.close(); + } catch (SQLException ex) { + // do nothing + } finally { + dbConnection = null; + } + } + } + + public int flush() { + int rowsUpdated = 0; + if (markers.size() > 0 && pstmt != null) { + try { + int[] updates = pstmt.executeBatch(); + for (int i : updates) { + rowsUpdated += normalizeUpdateCount(i); + } + markers.clear(); + } catch (BatchUpdateException ex) { + removeMarkersThatWereSuccessful(ex); + throw sqlConnection.translate(ex); + } catch (SQLException ex) { + throw sqlConnection.translate(ex); + } + } + return rowsUpdated; + } + + public T queryForObject(final String sql, Class clazz, final Object... args) { + return execute(this.dbConnection, new IConnectionCallback() { + @SuppressWarnings("unchecked") + public T execute(Connection con) throws SQLException { + T result = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + ps = con.prepareStatement(sql); + StatementCreatorUtil.setValues(ps, args); + rs = ps.executeQuery(); + if (rs.next()) { + result = (T) rs.getObject(1); + } + } finally { + JdbcSqlTemplate.close(rs); + JdbcSqlTemplate.close(ps); + } + return result; + } + }); + } + + public T execute(Connection c, IConnectionCallback callback) { + try { + return callback.execute(c); + } catch (SQLException ex) { + throw this.sqlConnection.translate(ex); + } + } + + /** + * According to the executeUpdate() javadoc -2 means that the result was + * successful, but that the number of rows affected is unknown. since we + * know that only one row is suppose to be affected, we'll default to 1. + * + * @param value + */ + protected final int normalizeUpdateCount(int value) { + if (value == Statement.SUCCESS_NO_INFO) { + value = 1; + } + return value; + } + + protected void removeMarkersThatWereSuccessful(BatchUpdateException ex) { + int[] updateCounts = ex.getUpdateCounts(); + Iterator it = markers.iterator(); + int index = 0; + while (it.hasNext()) { + it.next(); + if (updateCounts.length > index && normalizeUpdateCount(updateCounts[index]) > 0) { + it.remove(); + } + index++; + } + } + + public void prepare(String sql) { + try { + if (this.markers.size() > 0) { + throw new IllegalStateException( + "Cannot prepare a new batch before the last batch has been flushed."); + } + JdbcSqlTemplate.close(pstmt); + pstmt = dbConnection.prepareStatement(sql); + psql = sql; + } catch (SQLException ex) { + throw sqlConnection.translate(ex); + } + } + + public int update(Object marker) { + return update(marker, null, null); + } + + public int update(Object marker, Object[] args, int[] argTypes) { + int rowsUpdated = 0; + try { + if (args != null) { + StatementCreatorUtil.setValues(pstmt, args, argTypes, sqlConnection + .getLobHandler()); + } + if (inBatchMode) { + if (marker == null) { + marker = new Integer(markers.size() + 1); + } + markers.add(marker); + pstmt.addBatch(); + if (markers.size() >= numberOfRowsBeforeBatchFlush) { + rowsUpdated = flush(); + } + } else { + rowsUpdated = pstmt.executeUpdate(); + } + } catch (SQLException ex) { + throw sqlConnection.translate(ex); + } + return rowsUpdated; + } + + public List getUnflushedMarkers(boolean clear) { + List ret = new ArrayList(markers); + if (clear) { + markers.clear(); + } + return ret; + } + +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/StatementCreatorUtil.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/StatementCreatorUtil.java new file mode 100644 index 0000000000..1f85b2e45b --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/jdbc/StatementCreatorUtil.java @@ -0,0 +1,351 @@ +package org.jumpmind.symmetric.db.jdbc; + +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.StringWriter; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +import org.jumpmind.symmetric.util.Log; +import org.jumpmind.symmetric.util.LogFactory; +import org.jumpmind.symmetric.util.LogLevel; + +public abstract class StatementCreatorUtil { + + private static final Log log = LogFactory.getLog(StatementCreatorUtil.class); + + private static final int UNKNOWN_TYPE = Integer.MIN_VALUE; + + private static Map, Integer> javaTypeToSqlTypeMap = new HashMap, Integer>(32); + + static { + /* + * JDBC 3.0 only - not compatible with e.g. MySQL at present + * javaTypeToSqlTypeMap.put(boolean.class, new Integer(Types.BOOLEAN)); + * javaTypeToSqlTypeMap.put(Boolean.class, new Integer(Types.BOOLEAN)); + */ + javaTypeToSqlTypeMap.put(byte.class, Types.TINYINT); + javaTypeToSqlTypeMap.put(Byte.class, Types.TINYINT); + javaTypeToSqlTypeMap.put(short.class, Types.SMALLINT); + javaTypeToSqlTypeMap.put(Short.class, Types.SMALLINT); + javaTypeToSqlTypeMap.put(int.class, Types.INTEGER); + javaTypeToSqlTypeMap.put(Integer.class, Types.INTEGER); + javaTypeToSqlTypeMap.put(long.class, Types.BIGINT); + javaTypeToSqlTypeMap.put(Long.class, Types.BIGINT); + javaTypeToSqlTypeMap.put(BigInteger.class, Types.BIGINT); + javaTypeToSqlTypeMap.put(float.class, Types.FLOAT); + javaTypeToSqlTypeMap.put(Float.class, Types.FLOAT); + javaTypeToSqlTypeMap.put(double.class, Types.DOUBLE); + javaTypeToSqlTypeMap.put(Double.class, Types.DOUBLE); + javaTypeToSqlTypeMap.put(BigDecimal.class, Types.DECIMAL); + javaTypeToSqlTypeMap.put(java.sql.Date.class, Types.DATE); + javaTypeToSqlTypeMap.put(java.sql.Time.class, Types.TIME); + javaTypeToSqlTypeMap.put(java.sql.Timestamp.class, Types.TIMESTAMP); + javaTypeToSqlTypeMap.put(Blob.class, Types.BLOB); + javaTypeToSqlTypeMap.put(Clob.class, Types.CLOB); + } + + public static void setValues(PreparedStatement ps, Object[] args, int[] argTypes, + ILobHandler lobHandler) throws SQLException { + for (int i = 1; i <= args.length; i++) { + Object arg = args[i - 1]; + int argType = argTypes != null && argTypes.length > i ? argTypes[i - 1] : UNKNOWN_TYPE; + if (argType == Types.BLOB && lobHandler != null) { + lobHandler.setBlobAsBytes(ps, i, (byte[]) arg); + } else if (argType == Types.CLOB && lobHandler != null) { + lobHandler.setClobAsString(ps, i, (String) arg); + } else { + setParameterValue(ps, i, argType, arg); + } + } + } + + public static void setValues(PreparedStatement ps, Object[] args) throws SQLException { + if (args != null) { + for (int i = 0; i < args.length; i++) { + Object arg = args[i]; + doSetValue(ps, i + 1, arg); + } + } + } + + /** + * Set the value for prepared statements specified parameter index using the + * passed in value. This method can be overridden by sub-classes if needed. + * + * @param ps + * the PreparedStatement + * @param parameterPosition + * index of the parameter position + * @param argValue + * the value to set + * @throws SQLException + */ + public static void doSetValue(PreparedStatement ps, int parameterPosition, Object argValue) + throws SQLException { + setParameterValue(ps, parameterPosition, UNKNOWN_TYPE, argValue); + } + + /** + * Derive a default SQL type from the given Java type. + * + * @param javaType + * the Java type to translate + * @return the corresponding SQL type, or null if none found + */ + public static int javaTypeToSqlParameterType(Class javaType) { + Integer sqlType = javaTypeToSqlTypeMap.get(javaType); + if (sqlType != null) { + return sqlType; + } + if (Number.class.isAssignableFrom(javaType)) { + return Types.NUMERIC; + } + if (isStringValue(javaType)) { + return Types.VARCHAR; + } + if (isDateValue(javaType) || Calendar.class.isAssignableFrom(javaType)) { + return Types.TIMESTAMP; + } + return UNKNOWN_TYPE; + } + + /** + * Set the value for a parameter. The method used is based on the SQL type + * of the parameter and we can handle complex types like arrays and LOBs. + * + * @param ps + * the prepared statement or callable statement + * @param paramIndex + * index of the parameter we are setting + * @param sqlType + * the SQL type of the parameter + * @param inValue + * the value to set (plain value or a SqlTypeValue) + * @throws SQLException + * if thrown by PreparedStatement methods + * @see SqlTypeValue + */ + public static void setParameterValue(PreparedStatement ps, int paramIndex, int sqlType, + Object inValue) throws SQLException { + + setParameterValueInternal(ps, paramIndex, sqlType, null, null, inValue); + } + + /** + * Set the value for a parameter. The method used is based on the SQL type + * of the parameter and we can handle complex types like arrays and LOBs. + * + * @param ps + * the prepared statement or callable statement + * @param paramIndex + * index of the parameter we are setting + * @param sqlType + * the SQL type of the parameter + * @param typeName + * the type name of the parameter (optional, only used for SQL + * NULL and SqlTypeValue) + * @param inValue + * the value to set (plain value or a SqlTypeValue) + * @throws SQLException + * if thrown by PreparedStatement methods + * @see SqlTypeValue + */ + public static void setParameterValue(PreparedStatement ps, int paramIndex, int sqlType, + String typeName, Object inValue) throws SQLException { + + setParameterValueInternal(ps, paramIndex, sqlType, typeName, null, inValue); + } + + /** + * Set the value for a parameter. The method used is based on the SQL type + * of the parameter and we can handle complex types like arrays and LOBs. + * + * @param ps + * the prepared statement or callable statement + * @param paramIndex + * index of the parameter we are setting + * @param sqlType + * the SQL type of the parameter + * @param typeName + * the type name of the parameter (optional, only used for SQL + * NULL and SqlTypeValue) + * @param scale + * the number of digits after the decimal point (for DECIMAL and + * NUMERIC types) + * @param inValue + * the value to set (plain value or a SqlTypeValue) + * @throws SQLException + * if thrown by PreparedStatement methods + * @see SqlTypeValue + */ + private static void setParameterValueInternal(PreparedStatement ps, int paramIndex, + int sqlType, String typeName, Integer scale, Object inValue) throws SQLException { + + String typeNameToUse = typeName; + int sqlTypeToUse = sqlType; + Object inValueToUse = inValue; + + if (inValueToUse == null) { + setNull(ps, paramIndex, sqlTypeToUse, typeNameToUse); + } else { + setValue(ps, paramIndex, sqlTypeToUse, typeNameToUse, scale, inValueToUse); + } + } + + /** + * Set the specified PreparedStatement parameter to null, respecting + * database-specific peculiarities. + */ + private static void setNull(PreparedStatement ps, int paramIndex, int sqlType, String typeName) + throws SQLException { + + if (sqlType == UNKNOWN_TYPE) { + boolean useSetObject = false; + sqlType = Types.NULL; + try { + DatabaseMetaData dbmd = ps.getConnection().getMetaData(); + String databaseProductName = dbmd.getDatabaseProductName(); + String jdbcDriverName = dbmd.getDriverName(); + if (databaseProductName.startsWith("Informix") + || jdbcDriverName.startsWith("Microsoft SQL Server")) { + useSetObject = true; + } else if (databaseProductName.startsWith("DB2") + || jdbcDriverName.startsWith("jConnect") + || jdbcDriverName.startsWith("SQLServer") + || jdbcDriverName.startsWith("Apache Derby")) { + sqlType = Types.VARCHAR; + } + } catch (Throwable ex) { + log.log(LogLevel.DEBUG, "Could not check database or driver name", ex); + } + if (useSetObject) { + ps.setObject(paramIndex, null); + } else { + ps.setNull(paramIndex, sqlType); + } + } else if (typeName != null) { + ps.setNull(paramIndex, sqlType, typeName); + } else { + ps.setNull(paramIndex, sqlType); + } + } + + private static void setValue(PreparedStatement ps, int paramIndex, int sqlType, + String typeName, Integer scale, Object inValue) throws SQLException { + + if (sqlType == Types.VARCHAR || sqlType == Types.LONGVARCHAR + || (sqlType == Types.CLOB && isStringValue(inValue.getClass()))) { + ps.setString(paramIndex, inValue.toString()); + } else if (sqlType == Types.DECIMAL || sqlType == Types.NUMERIC) { + if (inValue instanceof BigDecimal) { + ps.setBigDecimal(paramIndex, (BigDecimal) inValue); + } else if (scale != null) { + ps.setObject(paramIndex, inValue, sqlType, scale); + } else { + ps.setObject(paramIndex, inValue, sqlType); + } + } else if (sqlType == Types.DATE) { + if (inValue instanceof java.util.Date) { + if (inValue instanceof java.sql.Date) { + ps.setDate(paramIndex, (java.sql.Date) inValue); + } else { + ps.setDate(paramIndex, new java.sql.Date(((java.util.Date) inValue).getTime())); + } + } else if (inValue instanceof Calendar) { + Calendar cal = (Calendar) inValue; + ps.setDate(paramIndex, new java.sql.Date(cal.getTime().getTime()), cal); + } else { + ps.setObject(paramIndex, inValue, Types.DATE); + } + } else if (sqlType == Types.TIME) { + if (inValue instanceof java.util.Date) { + if (inValue instanceof java.sql.Time) { + ps.setTime(paramIndex, (java.sql.Time) inValue); + } else { + ps.setTime(paramIndex, new java.sql.Time(((java.util.Date) inValue).getTime())); + } + } else if (inValue instanceof Calendar) { + Calendar cal = (Calendar) inValue; + ps.setTime(paramIndex, new java.sql.Time(cal.getTime().getTime()), cal); + } else { + ps.setObject(paramIndex, inValue, Types.TIME); + } + } else if (sqlType == Types.TIMESTAMP) { + if (inValue instanceof java.util.Date) { + if (inValue instanceof java.sql.Timestamp) { + ps.setTimestamp(paramIndex, (java.sql.Timestamp) inValue); + } else { + ps.setTimestamp(paramIndex, + new java.sql.Timestamp(((java.util.Date) inValue).getTime())); + } + } else if (inValue instanceof Calendar) { + Calendar cal = (Calendar) inValue; + ps.setTimestamp(paramIndex, new java.sql.Timestamp(cal.getTime().getTime()), cal); + } else { + ps.setObject(paramIndex, inValue, Types.TIMESTAMP); + } + } else if (sqlType == UNKNOWN_TYPE) { + if (isStringValue(inValue.getClass())) { + ps.setString(paramIndex, inValue.toString()); + } else if (isDateValue(inValue.getClass())) { + ps.setTimestamp(paramIndex, + new java.sql.Timestamp(((java.util.Date) inValue).getTime())); + } else if (inValue instanceof Calendar) { + Calendar cal = (Calendar) inValue; + ps.setTimestamp(paramIndex, new java.sql.Timestamp(cal.getTime().getTime()), cal); + } else { + // Fall back to generic setObject call without SQL type + // specified. + ps.setObject(paramIndex, inValue); + } + } else { + // Fall back to generic setObject call with SQL type specified. + ps.setObject(paramIndex, inValue, sqlType); + } + } + + /** + * Check whether the given value can be treated as a String value. + */ + private static boolean isStringValue(Class inValueType) { + // Consider any CharSequence (including StringBuffer and StringBuilder) + // as a String. + return (CharSequence.class.isAssignableFrom(inValueType) || StringWriter.class + .isAssignableFrom(inValueType)); + } + + /** + * Check whether the given value is a java.util.Date (but not + * one of the JDBC-specific subclasses). + */ + private static boolean isDateValue(Class inValueType) { + return (java.util.Date.class.isAssignableFrom(inValueType) && !(java.sql.Date.class + .isAssignableFrom(inValueType) || java.sql.Time.class.isAssignableFrom(inValueType) || java.sql.Timestamp.class + .isAssignableFrom(inValueType))); + } + +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/mapper/StringMapper.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/mapper/StringMapper.java new file mode 100644 index 0000000000..bb8b32beae --- /dev/null +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/db/mapper/StringMapper.java @@ -0,0 +1,11 @@ +package org.jumpmind.symmetric.db.mapper; + +import org.jumpmind.symmetric.db.ISqlRowMapper; +import org.jumpmind.symmetric.db.Row; + +public class StringMapper implements ISqlRowMapper { + + public String mapRow(Row row) { + return row.stringValue(); + } +} diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/ddl/model/Column.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/ddl/model/Column.java index cd7c4f5dfc..1d5303070a 100644 --- a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/ddl/model/Column.java +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/ddl/model/Column.java @@ -42,38 +42,56 @@ public class Column implements Cloneable, Serializable { /** The name of the column. */ private String name; + /** * The java name of the column (optional and unused by DdlUtils, for Torque * compatibility). */ private String javaName; + /** The column's description. */ private String description; + /** Whether the column is a primary key column. */ private boolean primaryKey; + /** * Whether the column is required, ie. it must not contain NULL * . */ private boolean required; + /** Whether the column's value is incremented automatically. */ private boolean autoIncrement; + /** The JDBC type code, one of the constants in {@link java.sql.Types}. */ private int typeCode; + /** The name of the JDBC type. */ private String type; + /** The size of the column for JDBC types that require/support this. */ private String size; + /** The size of the column for JDBC types that require/support this. */ private Integer sizeAsInt; + /** The scale of the column for JDBC types that require/support this. */ private int scale; + /** The default value. */ private String defaultValue; private String jdbcTypeName; private boolean distributionKey; + + public Column() { + } + + public Column(String name) { + this.name = name; + } /** * Returns the name of the column. diff --git a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/ddl/model/Table.java b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/ddl/model/Table.java index f1ef1e6630..4e92903f08 100644 --- a/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/ddl/model/Table.java +++ b/symmetric/symmetric-ddl/src/main/java/org/jumpmind/symmetric/ddl/model/Table.java @@ -820,25 +820,51 @@ public String toVerboseString() { } public String getFullyQualifiedTableName() { - return getFullyQualifiedTableName(name, schema, catalog); + return getFullyQualifiedTableName(catalog, schema, name, null); } - public static String getFullyQualifiedTableName(String tableName, String schemaName, - String catalogName) { - return getQualifiedTablePrefix(schemaName, catalogName) + tableName; + public static String getFullyQualifiedTableName(String catalogName, String schemaName, + String tableName) { + return getFullyQualifiedTableName(catalogName, schemaName, tableName, null); } - public static String getQualifiedTablePrefix(String schemaName, String catalogName) { + public static String getQualifiedTablePrefix(String catalogName, String schemaName) { + return getQualifiedTablePrefix(catalogName, schemaName, null); + } + + public String getFullyQualifiedTableName(String quoteString) { + return getFullyQualifiedTableName(catalog, schema, name, quoteString); + } + + public static String getFullyQualifiedTableName(String catalogName, String schemaName, + String tableName, String quoteString) { + if (quoteString == null) { + quoteString = ""; + } + return getQualifiedTablePrefix(catalogName, schemaName, quoteString) + quoteString + + tableName + quoteString; + } + + public static String getQualifiedTablePrefix(String catalogName, String schemaName, + String quoteString) { + if (quoteString == null) { + quoteString = ""; + } String fullyQualified = ""; if (!StringUtils.isBlank(schemaName)) { - fullyQualified = schemaName + "." + fullyQualified; + fullyQualified = quoteString + schemaName + quoteString + "." + fullyQualified; } if (!StringUtils.isBlank(catalogName)) { - fullyQualified = catalogName + "." + fullyQualified; + fullyQualified = quoteString + catalogName + quoteString + "." + fullyQualified; } return fullyQualified; } + public String getQualifiedTablePrefix(String quoteString) { + return getQualifiedTablePrefix(schema, catalog, quoteString); + } + + public Column getColumnWithName(String name) { Column[] columns = getColumns(); if (columns != null) { diff --git a/symmetric/symmetric-io/pom.xml b/symmetric/symmetric-io/pom.xml index 57f21e2aec..1d179a74be 100644 --- a/symmetric/symmetric-io/pom.xml +++ b/symmetric/symmetric-io/pom.xml @@ -18,6 +18,14 @@ + + org.jumpmind.symmetric + symmetric-ddl + + + org.jumpmind.symmetric + symmetric-csv + junit junit diff --git a/symmetric/symmetric-parent/pom.xml b/symmetric/symmetric-parent/pom.xml index c796b5e9eb..1aacc8da70 100644 --- a/symmetric/symmetric-parent/pom.xml +++ b/symmetric/symmetric-parent/pom.xml @@ -284,6 +284,11 @@ + + org.jumpmind.symmetric + symmetric-util + 2.6.0-SNAPSHOT + org.jumpmind.symmetric symmetric-ddl @@ -295,6 +300,11 @@ + + org.jumpmind.symmetric + symmetric-io + 2.6.0-SNAPSHOT + org.jumpmind.symmetric symmetric-csv diff --git a/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/integrate/TemplatedPublisherDataLoaderFilter.java b/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/integrate/TemplatedPublisherDataLoaderFilter.java index 7fbf277fb5..a8a1dc11cc 100644 --- a/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/integrate/TemplatedPublisherDataLoaderFilter.java +++ b/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/integrate/TemplatedPublisherDataLoaderFilter.java @@ -25,9 +25,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jumpmind.symmetric.db.DmlStatement.DmlType; import org.jumpmind.symmetric.load.IDataLoaderContext; import org.jumpmind.symmetric.load.IDataLoaderFilter; -import org.jumpmind.symmetric.load.StatementBuilder.DmlType; /** * A convenience class that allows the end user to template a message using diff --git a/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/map/AddColumnsFilter.java b/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/map/AddColumnsFilter.java index 9e3b99dfe6..76fb8e536a 100644 --- a/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/map/AddColumnsFilter.java +++ b/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/map/AddColumnsFilter.java @@ -27,11 +27,11 @@ import org.jumpmind.symmetric.common.TokenConstants; import org.jumpmind.symmetric.common.logging.ILog; import org.jumpmind.symmetric.common.logging.LogFactory; +import org.jumpmind.symmetric.db.DmlStatement.DmlType; import org.jumpmind.symmetric.ddl.model.Table; import org.jumpmind.symmetric.ext.INodeGroupExtensionPoint; import org.jumpmind.symmetric.load.IDataLoaderContext; import org.jumpmind.symmetric.load.ITableColumnFilter; -import org.jumpmind.symmetric.load.StatementBuilder.DmlType; /** * A column filter that can add additional columns to a table that is being loaded diff --git a/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/map/ChangeColumnsNamesFilter.java b/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/map/ChangeColumnsNamesFilter.java index cf0bd0307e..186285aac2 100644 --- a/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/map/ChangeColumnsNamesFilter.java +++ b/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/map/ChangeColumnsNamesFilter.java @@ -23,11 +23,11 @@ import java.util.Map; import java.util.TreeMap; +import org.jumpmind.symmetric.db.DmlStatement.DmlType; import org.jumpmind.symmetric.ddl.model.Table; import org.jumpmind.symmetric.ext.INodeGroupExtensionPoint; import org.jumpmind.symmetric.load.IDataLoaderContext; import org.jumpmind.symmetric.load.ITableColumnFilter; -import org.jumpmind.symmetric.load.StatementBuilder.DmlType; /** * diff --git a/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/map/RemoveColumnsFilter.java b/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/map/RemoveColumnsFilter.java index 162b930934..b72d974339 100644 --- a/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/map/RemoveColumnsFilter.java +++ b/symmetric/symmetric-server/src/main/java/org/jumpmind/symmetric/map/RemoveColumnsFilter.java @@ -26,11 +26,11 @@ import java.util.Map; import org.apache.commons.lang.ArrayUtils; +import org.jumpmind.symmetric.db.DmlStatement.DmlType; import org.jumpmind.symmetric.ddl.model.Table; import org.jumpmind.symmetric.ext.INodeGroupExtensionPoint; import org.jumpmind.symmetric.load.IDataLoaderContext; import org.jumpmind.symmetric.load.ITableColumnFilter; -import org.jumpmind.symmetric.load.StatementBuilder.DmlType; /** * This filter may be configured to prevent specific columns from being loaded diff --git a/symmetric/symmetric-util/pom.xml b/symmetric/symmetric-util/pom.xml index 8773ab3e26..22d8d6f639 100644 --- a/symmetric/symmetric-util/pom.xml +++ b/symmetric/symmetric-util/pom.xml @@ -1,63 +1,37 @@ - - 4.0.0 - org.jumpmind.symmetric - symmetric-util - jar - 2.6.0-SNAPSHOT - ddl - 2011 + + 4.0.0 + org.jumpmind.symmetric + symmetric-util + jar + 2.6.0-SNAPSHOT + ddl + 2011 - + - - - org.jumpmind.symmetric - symmetric-parent - 2.6.0-SNAPSHOT - ../symmetric-parent/pom.xml - - - - junit - junit - test - - - - org.apache.derby - derby - true - - - org.apache.derby - derbytools - true - - - mysql - mysql-connector-java - true - - - postgresql - postgresql - true - - - net.sourceforge.jtds - jtds - true - - - org.hsqldb - hsqldb - true - - - com.h2database - h2 - true - - + + org.jumpmind.symmetric + symmetric-parent + 2.6.0-SNAPSHOT + ../symmetric-parent/pom.xml + + + + + commons-lang + commons-lang + + + log4j + log4j + provided + + + junit + junit + test + + diff --git a/symmetric/symmetric-util/src/main/java/org/jumpmind/symmetric/util/ConsoleLog.java b/symmetric/symmetric-util/src/main/java/org/jumpmind/symmetric/util/ConsoleLog.java new file mode 100644 index 0000000000..5ff7a20cda --- /dev/null +++ b/symmetric/symmetric-util/src/main/java/org/jumpmind/symmetric/util/ConsoleLog.java @@ -0,0 +1,60 @@ +package org.jumpmind.symmetric.util; + +import org.apache.commons.lang.StringUtils; + + +public class ConsoleLog extends Log { + + @Override + public boolean isDebugEnabled() { + return getLogLevel().contains("debug"); + } + + public boolean isInfoEnabled() { + return isDebugEnabled() || getLogLevel().contains("info"); + } + + public boolean isWarnEnabled() { + return isInfoEnabled() || getLogLevel().contains("warn"); + } + + protected String getLogLevel() { + return System.getProperty("log", "info").toLowerCase(); + } + + protected boolean isLogLevelEnabled(LogLevel level) { + switch (level) { + case DEBUG: + return isDebugEnabled(); + case INFO: + return isInfoEnabled() || isDebugEnabled(); + case WARN: + return isWarnEnabled() || isInfoEnabled() || isDebugEnabled(); + case ERROR: + return true; + default: + return false; + } + } + + public void log(LogLevel level, Throwable error, String msg, Object... params) { + if (isLogLevelEnabled(level)) { + if (StringUtils.isNotBlank(msg) && params != null && params.length > 0) { + msg = String.format(msg, params); + } + + if (StringUtils.isNotBlank(msg)) { + if (msg.endsWith("\r")) { + System.out.print(msg); + } else { + System.out.println(msg); + } + } + + if (error != null) { + error.printStackTrace(); + } + } + + } +} diff --git a/symmetric/symmetric-util/src/main/java/org/jumpmind/symmetric/util/Log.java b/symmetric/symmetric-util/src/main/java/org/jumpmind/symmetric/util/Log.java new file mode 100644 index 0000000000..3be14e0258 --- /dev/null +++ b/symmetric/symmetric-util/src/main/java/org/jumpmind/symmetric/util/Log.java @@ -0,0 +1,47 @@ +package org.jumpmind.symmetric.util; + +abstract public class Log { + + protected Class clazz; + + public abstract void log(LogLevel level, Throwable error, String msg, Object... params); + + public void log(LogLevel level, String msg, Object... params) { + log(level, null, msg, params); + } + + public void log(LogLevel level, Throwable error) { + log(level, error, null); + } + + public void debug(String msg, Object... params) { + log(LogLevel.DEBUG, msg, params); + } + + public void info(String msg, Object... params) { + log(LogLevel.INFO, msg, params); + } + + public void warn(String msg, Object... params) { + log(LogLevel.WARN, msg, params); + } + + public void error(String msg, Object... params) { + log(LogLevel.ERROR, msg, params); + } + + public void error(Throwable ex, String msg, Object... params) { + log(LogLevel.ERROR, ex, msg, params); + } + + public void error(Throwable ex) { + log(LogLevel.ERROR, ex); + } + + public abstract boolean isDebugEnabled(); + + protected void initialize(Class clazz) { + this.clazz = clazz; + } + +} diff --git a/symmetric/symmetric-util/src/main/java/org/jumpmind/symmetric/util/Log4jLog.java b/symmetric/symmetric-util/src/main/java/org/jumpmind/symmetric/util/Log4jLog.java new file mode 100644 index 0000000000..b79fec7fb1 --- /dev/null +++ b/symmetric/symmetric-util/src/main/java/org/jumpmind/symmetric/util/Log4jLog.java @@ -0,0 +1,66 @@ +package org.jumpmind.symmetric.util; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +public class Log4jLog extends Log { + + Logger logger; + + @Override + protected void initialize(Class clazz) { + super.initialize(clazz); + logger = Logger.getLogger(clazz.getName()); + } + + public void log(LogLevel level, String msg, Object... params) { + log(level, null, msg, params); + } + + public void log(LogLevel level, Throwable error) { + log(level, error, null); + } + + @Override + public void error(Throwable ex) { + log(LogLevel.ERROR, ex); + } + + @Override + public void debug(String msg, Object... params) { + log(LogLevel.DEBUG, msg, params); + } + + @Override + public boolean isDebugEnabled() { + return logger.isDebugEnabled(); + } + + public void log(LogLevel level, Throwable error, String msg, Object... params) { + Level loggerLevel = Level.FATAL; + + switch (level) { + case DEBUG: + loggerLevel = Level.DEBUG; + break; + case INFO: + loggerLevel = Level.INFO; + break; + case WARN: + loggerLevel = Level.WARN; + break; + } + + if (logger.isEnabledFor(loggerLevel)) { + if (error != null && params == null) { + logger.log(loggerLevel, msg, error); + } else if (error != null && msg != null && params != null) { + logger.log(loggerLevel, String.format(msg, params)); + logger.log(loggerLevel, error.getMessage(), error); + } else if (msg != null) { + logger.log(loggerLevel, String.format(msg, params)); + } + } + + } +} diff --git a/symmetric/symmetric-util/src/main/java/org/jumpmind/symmetric/util/LogFactory.java b/symmetric/symmetric-util/src/main/java/org/jumpmind/symmetric/util/LogFactory.java new file mode 100644 index 0000000000..e93223572e --- /dev/null +++ b/symmetric/symmetric-util/src/main/java/org/jumpmind/symmetric/util/LogFactory.java @@ -0,0 +1,54 @@ +package org.jumpmind.symmetric.util; + +import java.util.HashMap; +import java.util.Map; + +public class LogFactory { + + private static Class logClass; + + private static Map, Log> logs = new HashMap, Log>(); + + public static void setLogClass(Class logClass) { + LogFactory.logClass = logClass; + logs.clear(); + } + + static void checkInitialization() { + if (logClass == null) { + String clazzName = System.getProperty(Log.class.getName(), ConsoleLog.class.getName()); + try { + logClass = Class.forName(clazzName); + Object log = logClass.newInstance(); + if (!(log instanceof Log)) { + throw new ClassCastException(log.getClass().getName() + + " was not an instance of " + Log.class.getName()); + } + } catch (Exception e) { + e.printStackTrace(); + logClass = Log4jLog.class; + } + } + } + + public static Log getLog(Class clazz) { + Log log = logs.get(clazz); + if (log == null) { + synchronized (logs) { + log = logs.get(clazz); + if (log == null) { + checkInitialization(); + try { + log = (Log) logClass.newInstance(); + } catch (Exception e) { + log = new Log4jLog(); + } + + log.initialize(clazz); + logs.put(clazz, log); + } + } + } + return log; + } +} diff --git a/symmetric/symmetric-util/src/main/java/org/jumpmind/symmetric/util/LogLevel.java b/symmetric/symmetric-util/src/main/java/org/jumpmind/symmetric/util/LogLevel.java new file mode 100644 index 0000000000..ede76bd01d --- /dev/null +++ b/symmetric/symmetric-util/src/main/java/org/jumpmind/symmetric/util/LogLevel.java @@ -0,0 +1,5 @@ +package org.jumpmind.symmetric.util; + +public enum LogLevel { + DEBUG, INFO, WARN, ERROR, FATAL +}