You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metamodel.apache.org by ka...@apache.org on 2013/07/22 10:11:02 UTC
[49/64] [partial] Hard rename of all 'org/eobjects' folders to
'org/apache'.
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/QueryPostprocessDataContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/QueryPostprocessDataContext.java b/core/src/main/java/org/apache/metamodel/QueryPostprocessDataContext.java
new file mode 100644
index 0000000..d84da6a
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/QueryPostprocessDataContext.java
@@ -0,0 +1,589 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eobjects.metamodel.convert.ConvertedDataSetInterceptor;
+import org.eobjects.metamodel.convert.Converters;
+import org.eobjects.metamodel.convert.HasReadTypeConverters;
+import org.eobjects.metamodel.convert.TypeConverter;
+import org.eobjects.metamodel.data.DataSet;
+import org.eobjects.metamodel.data.DataSetHeader;
+import org.eobjects.metamodel.data.DefaultRow;
+import org.eobjects.metamodel.data.EmptyDataSet;
+import org.eobjects.metamodel.data.FirstRowDataSet;
+import org.eobjects.metamodel.data.InMemoryDataSet;
+import org.eobjects.metamodel.data.MaxRowsDataSet;
+import org.eobjects.metamodel.data.Row;
+import org.eobjects.metamodel.data.SimpleDataSetHeader;
+import org.eobjects.metamodel.query.FilterItem;
+import org.eobjects.metamodel.query.FromItem;
+import org.eobjects.metamodel.query.GroupByItem;
+import org.eobjects.metamodel.query.JoinType;
+import org.eobjects.metamodel.query.OperatorType;
+import org.eobjects.metamodel.query.OrderByItem;
+import org.eobjects.metamodel.query.Query;
+import org.eobjects.metamodel.query.SelectItem;
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.schema.ColumnType;
+import org.eobjects.metamodel.schema.MutableColumn;
+import org.eobjects.metamodel.schema.MutableRelationship;
+import org.eobjects.metamodel.schema.MutableSchema;
+import org.eobjects.metamodel.schema.MutableTable;
+import org.eobjects.metamodel.schema.Relationship;
+import org.eobjects.metamodel.schema.Schema;
+import org.eobjects.metamodel.schema.Table;
+import org.eobjects.metamodel.schema.TableType;
+import org.eobjects.metamodel.util.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract DataContext for data sources that do not support SQL queries
+ * natively.
+ *
+ * Instead this superclass only requires that a subclass can materialize a
+ * single table at a time. Then the query will be executed by post processing
+ * the datasets client-side.
+ */
+public abstract class QueryPostprocessDataContext extends AbstractDataContext implements HasReadTypeConverters {
+
+ private static final Logger logger = LoggerFactory.getLogger(QueryPostprocessDataContext.class);
+
+ public static final String INFORMATION_SCHEMA_NAME = "information_schema";
+
+ private final Map<Column, TypeConverter<?, ?>> _converters;
+
+ private Schema _mainSchema;
+
+ public QueryPostprocessDataContext() {
+ super();
+ _converters = new HashMap<Column, TypeConverter<?, ?>>();
+ }
+
+ @Override
+ public DataSet executeQuery(final Query query) {
+ final List<SelectItem> selectItems = query.getSelectClause().getItems();
+ final List<FromItem> fromItems = query.getFromClause().getItems();
+ final List<FilterItem> whereItems = query.getWhereClause().getItems();
+ final List<SelectItem> whereSelectItems = query.getWhereClause().getEvaluatedSelectItems();
+ final List<GroupByItem> groupByItems = query.getGroupByClause().getItems();
+ final List<SelectItem> groupBySelectItems = query.getGroupByClause().getEvaluatedSelectItems();
+ final List<SelectItem> havingSelectItems = query.getHavingClause().getEvaluatedSelectItems();
+ final List<SelectItem> orderBySelectItems = query.getOrderByClause().getEvaluatedSelectItems();
+
+ final List<FilterItem> havingItems = query.getHavingClause().getItems();
+ final List<OrderByItem> orderByItems = query.getOrderByClause().getItems();
+
+ // check for approximate SELECT COUNT(*) queries
+ if (fromItems.size() == 1 && selectItems.size() == 1 && groupByItems.isEmpty() && havingItems.isEmpty()) {
+ final SelectItem selectItem = query.getSelectClause().getItem(0);
+ if (SelectItem.isCountAllItem(selectItem)) {
+ final boolean functionApproximationAllowed = selectItem.isFunctionApproximationAllowed();
+ final FromItem fromItem = query.getFromClause().getItem(0);
+ final Table table = fromItem.getTable();
+ if (table != null) {
+ if (isMainSchemaTable(table)) {
+ logger.debug("Query is a COUNT query with {} where items. Trying executeCountQuery(...)",
+ whereItems.size());
+ final Number count = executeCountQuery(table, whereItems, functionApproximationAllowed);
+ if (count == null) {
+ logger.debug("DataContext did not return any count query results. Proceeding with manual counting.");
+ } else {
+ List<Row> data = new ArrayList<Row>(1);
+ final DataSetHeader header = new SimpleDataSetHeader(new SelectItem[] { selectItem });
+ data.add(new DefaultRow(header, new Object[] { count }));
+ return new InMemoryDataSet(header, data);
+ }
+ }
+ }
+ }
+ }
+
+ final int firstRow = (query.getFirstRow() == null ? 1 : query.getFirstRow());
+ final int maxRows = (query.getMaxRows() == null ? -1 : query.getMaxRows());
+
+ // Check for very simple queries with max rows property set (typically
+ // preview), see Ticket #187
+ previewTable: if (whereItems.isEmpty() && groupByItems.isEmpty() && havingItems.isEmpty()
+ && orderByItems.isEmpty() && fromItems.size() == 1) {
+
+ final Table table = fromItems.get(0).getTable();
+ if (table != null) {
+ for (SelectItem item : selectItems) {
+ if (item.getFunction() != null || item.getExpression() != null) {
+ break previewTable;
+ }
+ }
+
+ DataSet dataSet = materializeTable(table, selectItems, firstRow, maxRows);
+ dataSet = MetaModelHelper.getSelection(selectItems, dataSet);
+ return dataSet;
+ }
+ }
+
+ // Creates a list for all select items that are needed to execute query
+ // (some may only be used as part of a filter, but not shown in result)
+ List<SelectItem> workSelectItems = CollectionUtils.concat(true, selectItems, whereSelectItems,
+ groupBySelectItems, havingSelectItems, orderBySelectItems);
+
+ // Materialize the tables in the from clause
+ final DataSet[] fromDataSets = new DataSet[fromItems.size()];
+ for (int i = 0; i < fromDataSets.length; i++) {
+ FromItem fromItem = fromItems.get(i);
+ fromDataSets[i] = materializeFromItem(fromItem, workSelectItems);
+ }
+
+ // Execute the query using the raw data
+ DataSet dataSet = MetaModelHelper.getCarthesianProduct(fromDataSets, whereItems);
+
+ // we can now exclude the select items imposed by the WHERE clause (and
+ // should, to make the aggregation process faster)
+ workSelectItems = CollectionUtils.concat(true, selectItems, groupBySelectItems, havingSelectItems,
+ orderBySelectItems);
+
+ if (groupByItems.size() > 0) {
+ dataSet = MetaModelHelper.getGrouped(workSelectItems, dataSet, groupByItems);
+ } else {
+ dataSet = MetaModelHelper.getAggregated(workSelectItems, dataSet);
+ }
+ dataSet = MetaModelHelper.getFiltered(dataSet, havingItems);
+
+ if (query.getSelectClause().isDistinct()) {
+ dataSet = MetaModelHelper.getSelection(selectItems, dataSet);
+ dataSet = MetaModelHelper.getDistinct(dataSet);
+ dataSet = MetaModelHelper.getOrdered(dataSet, orderByItems);
+ } else {
+ dataSet = MetaModelHelper.getOrdered(dataSet, orderByItems);
+ dataSet = MetaModelHelper.getSelection(selectItems, dataSet);
+ }
+
+ if (firstRow > 1) {
+ dataSet = new FirstRowDataSet(dataSet, firstRow);
+ }
+ if (maxRows != -1) {
+ dataSet = new MaxRowsDataSet(dataSet, maxRows);
+ }
+ return dataSet;
+ }
+
+ /**
+ * Executes a simple count query, if possible. This method is provided to
+ * allow subclasses to optimize count queries since they are quite common
+ * and often a datastore can retrieve the count using some specialized means
+ * which is much more performant than counting all records manually.
+ *
+ * @param table
+ * the table on which the count is requested.
+ * @param whereItems
+ * a (sometimes empty) list of WHERE items.
+ * @param functionApproximationAllowed
+ * whether approximation is allowed or not.
+ * @return the count of the particular table, or null if not available.
+ */
+ protected Number executeCountQuery(Table table, List<FilterItem> whereItems, boolean functionApproximationAllowed) {
+ return null;
+ }
+
+ protected DataSet materializeFromItem(final FromItem fromItem, final List<SelectItem> selectItems) {
+ DataSet dataSet;
+ JoinType joinType = fromItem.getJoin();
+ if (fromItem.getTable() != null) {
+ // We need to materialize a single table
+ final Table table = fromItem.getTable();
+ final List<SelectItem> selectItemsToMaterialize = new ArrayList<SelectItem>();
+
+ for (final SelectItem selectItem : selectItems) {
+ final FromItem selectedFromItem = selectItem.getFromItem();
+ if (selectedFromItem != null) {
+ if (selectedFromItem.equals(fromItem)) {
+ selectItemsToMaterialize.add(selectItem.replaceFunction(null));
+ }
+ } else {
+ // the select item does not specify a specific
+ // from-item
+ final Column selectedColumn = selectItem.getColumn();
+ if (selectedColumn != null) {
+ // we assume that if the table matches, we will use the
+ // column
+ if (selectedColumn.getTable() != null && selectedColumn.getTable().equals(table)) {
+ selectItemsToMaterialize.add(selectItem.replaceFunction(null));
+ }
+ }
+ }
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("calling materializeTable(" + table.getName() + "," + selectItemsToMaterialize + ",1,-1");
+ }
+
+ // Dispatching to the concrete subclass of
+ // QueryPostprocessDataContextStrategy
+ dataSet = materializeTable(table, selectItemsToMaterialize, 1, -1);
+
+ } else if (joinType != null) {
+ // We need to (recursively) materialize a joined FromItem
+ if (fromItem.getLeftSide() == null || fromItem.getRightSide() == null) {
+ throw new IllegalArgumentException("Joined FromItem requires both left and right side: " + fromItem);
+ }
+ DataSet[] fromItemDataSets = new DataSet[2];
+
+ // materialize left side
+ List<SelectItem> leftOn = Arrays.asList(fromItem.getLeftOn());
+ fromItemDataSets[0] = materializeFromItem(fromItem.getLeftSide(),
+ CollectionUtils.concat(true, selectItems, leftOn));
+
+ // materialize right side
+ List<SelectItem> rightOn = Arrays.asList(fromItem.getRightOn());
+ fromItemDataSets[1] = materializeFromItem(fromItem.getRightSide(),
+ CollectionUtils.concat(true, selectItems, rightOn));
+
+ FilterItem[] onConditions = new FilterItem[leftOn.size()];
+ for (int i = 0; i < onConditions.length; i++) {
+ FilterItem whereItem = new FilterItem(leftOn.get(i), OperatorType.EQUALS_TO, rightOn.get(i));
+ onConditions[i] = whereItem;
+ }
+ if (joinType == JoinType.INNER) {
+ dataSet = MetaModelHelper.getCarthesianProduct(fromItemDataSets, onConditions);
+ } else if (joinType == JoinType.LEFT) {
+ dataSet = MetaModelHelper.getLeftJoin(fromItemDataSets[0], fromItemDataSets[1], onConditions);
+ } else if (joinType == JoinType.RIGHT) {
+ dataSet = MetaModelHelper.getRightJoin(fromItemDataSets[0], fromItemDataSets[1], onConditions);
+ } else {
+ throw new IllegalArgumentException("FromItem type not supported: " + fromItem);
+ }
+ } else if (fromItem.getSubQuery() != null) {
+ // We need to (recursively) materialize a subquery
+ dataSet = executeQuery(fromItem.getSubQuery());
+ } else {
+ throw new IllegalArgumentException("FromItem type not supported: " + fromItem);
+ }
+ if (dataSet == null) {
+ throw new IllegalStateException("FromItem was not succesfully materialized: " + fromItem);
+ }
+ return dataSet;
+ }
+
+ protected DataSet materializeTable(final Table table, final List<SelectItem> selectItems, final int firstRow,
+ final int maxRows) {
+ if (table == null) {
+ throw new IllegalArgumentException("Table cannot be null");
+ }
+
+ if (selectItems == null || selectItems.isEmpty()) {
+ // add any column (typically this occurs because of COUNT(*)
+ // queries)
+ Column[] columns = table.getColumns();
+ if (columns.length == 0) {
+ logger.warn("Queried table has no columns: {}", table);
+ } else {
+ selectItems.add(new SelectItem(columns[0]));
+ }
+ }
+
+ if (maxRows == 0) {
+ return new EmptyDataSet(selectItems);
+ }
+
+ final Schema schema = table.getSchema();
+ final String schemaName;
+ if (schema == null) {
+ schemaName = null;
+ } else {
+ schemaName = schema.getName();
+ }
+
+ final DataSet dataSet;
+ if (INFORMATION_SCHEMA_NAME.equals(schemaName)) {
+ final DataSet informationDataSet = materializeInformationSchemaTable(table, selectItems, maxRows);
+ if (firstRow > 1) {
+ dataSet = new FirstRowDataSet(informationDataSet, firstRow);
+ } else {
+ dataSet = informationDataSet;
+ }
+ } else {
+ final DataSet tableDataSet = materializeMainSchemaTable(table, selectItems, firstRow, maxRows);
+
+ // conversion is done at materialization time, since it enables
+ // the refined types to be used also in eg. where clauses.
+ dataSet = new ConvertedDataSetInterceptor(_converters).intercept(tableDataSet);
+ }
+
+ return dataSet;
+ }
+
+ protected boolean isMainSchemaTable(Table table) {
+ Schema schema = table.getSchema();
+ if (INFORMATION_SCHEMA_NAME.equals(schema.getName())) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ protected final String[] getSchemaNamesInternal() throws MetaModelException {
+ final String[] schemaNames = new String[2];
+ schemaNames[0] = INFORMATION_SCHEMA_NAME;
+ schemaNames[1] = getMainSchemaName();
+ return schemaNames;
+ }
+
+ @Override
+ protected String getDefaultSchemaName() throws MetaModelException {
+ return getMainSchemaName();
+ }
+
+ @Override
+ protected final Schema getSchemaByNameInternal(final String name) throws MetaModelException {
+ final String mainSchemaName = getMainSchemaName();
+ if (name == null) {
+ if (mainSchemaName == null) {
+ return getMainSchema();
+ }
+ return null;
+ }
+
+ if (name.equalsIgnoreCase(mainSchemaName)) {
+ return getMainSchemaInternal();
+ } else if (name.equals(INFORMATION_SCHEMA_NAME)) {
+ return getInformationSchema();
+ }
+
+ logger.warn("Could not find matching schema of name '{}'. Main schema name is: '{}'. Returning null.", name,
+ mainSchemaName);
+ return null;
+ }
+
+ private Schema getInformationSchema() {
+ // Create schema
+ MutableSchema informationSchema = new MutableSchema(INFORMATION_SCHEMA_NAME);
+ MutableTable tablesTable = new MutableTable("tables", TableType.TABLE, informationSchema);
+ MutableTable columnsTable = new MutableTable("columns", TableType.TABLE, informationSchema);
+ MutableTable relationshipsTable = new MutableTable("relationships", TableType.TABLE, informationSchema);
+ informationSchema.addTable(tablesTable).addTable(columnsTable).addTable(relationshipsTable);
+
+ // Create "tables" table: name, type, num_columns, remarks
+ tablesTable.addColumn(new MutableColumn("name", ColumnType.VARCHAR, tablesTable, 0, false));
+ tablesTable.addColumn(new MutableColumn("type", ColumnType.VARCHAR, tablesTable, 1, true));
+ tablesTable.addColumn(new MutableColumn("num_columns", ColumnType.INTEGER, tablesTable, 2, true));
+ tablesTable.addColumn(new MutableColumn("remarks", ColumnType.VARCHAR, tablesTable, 3, true));
+
+ // Create "columns" table: name, type, native_type, size, nullable,
+ // indexed, table, remarks
+ columnsTable.addColumn(new MutableColumn("name", ColumnType.VARCHAR, columnsTable, 0, false));
+ columnsTable.addColumn(new MutableColumn("type", ColumnType.VARCHAR, columnsTable, 1, true));
+ columnsTable.addColumn(new MutableColumn("native_type", ColumnType.VARCHAR, columnsTable, 2, true));
+ columnsTable.addColumn(new MutableColumn("size", ColumnType.INTEGER, columnsTable, 3, true));
+ columnsTable.addColumn(new MutableColumn("nullable", ColumnType.BOOLEAN, columnsTable, 4, true));
+ columnsTable.addColumn(new MutableColumn("indexed", ColumnType.BOOLEAN, columnsTable, 5, true));
+ columnsTable.addColumn(new MutableColumn("table", ColumnType.VARCHAR, columnsTable, 6, false));
+ columnsTable.addColumn(new MutableColumn("remarks", ColumnType.VARCHAR, columnsTable, 7, true));
+
+ // Create "relationships" table: primary_table, primary_column,
+ // foreign_table, foreign_column
+ relationshipsTable.addColumn(new MutableColumn("primary_table", ColumnType.VARCHAR, relationshipsTable, 0,
+ false));
+ relationshipsTable.addColumn(new MutableColumn("primary_column", ColumnType.VARCHAR, relationshipsTable, 1,
+ false));
+ relationshipsTable.addColumn(new MutableColumn("foreign_table", ColumnType.VARCHAR, relationshipsTable, 2,
+ false));
+ relationshipsTable.addColumn(new MutableColumn("foreign_column", ColumnType.VARCHAR, relationshipsTable, 3,
+ false));
+
+ MutableRelationship.createRelationship(tablesTable.getColumnByName("name"),
+ columnsTable.getColumnByName("table"));
+ MutableRelationship.createRelationship(tablesTable.getColumnByName("name"),
+ relationshipsTable.getColumnByName("primary_table"));
+ MutableRelationship.createRelationship(tablesTable.getColumnByName("name"),
+ relationshipsTable.getColumnByName("foreign_table"));
+ MutableRelationship.createRelationship(columnsTable.getColumnByName("name"),
+ relationshipsTable.getColumnByName("primary_column"));
+ MutableRelationship.createRelationship(columnsTable.getColumnByName("name"),
+ relationshipsTable.getColumnByName("foreign_column"));
+
+ return informationSchema;
+ }
+
+ private DataSet materializeInformationSchemaTable(final Table table, final List<SelectItem> selectItems,
+ final int maxRows) {
+ final String tableName = table.getName();
+ final SelectItem[] columnSelectItems = MetaModelHelper.createSelectItems(table.getColumns());
+ final SimpleDataSetHeader header = new SimpleDataSetHeader(columnSelectItems);
+ final Table[] tables = getMainSchemaInternal().getTables();
+ final List<Row> data = new ArrayList<Row>();
+ if ("tables".equals(tableName)) {
+ // "tables" columns: name, type, num_columns, remarks
+ for (Table t : tables) {
+ String typeString = null;
+ if (t.getType() != null) {
+ typeString = t.getType().toString();
+ }
+ data.add(new DefaultRow(header, new Object[] { t.getName(), typeString, t.getColumnCount(),
+ t.getRemarks() }));
+ }
+ } else if ("columns".equals(tableName)) {
+ // "columns" columns: name, type, native_type, size, nullable,
+ // indexed, table, remarks
+ for (Table t : tables) {
+ for (Column c : t.getColumns()) {
+ String typeString = null;
+ if (t.getType() != null) {
+ typeString = c.getType().toString();
+ }
+ data.add(new DefaultRow(header, new Object[] { c.getName(), typeString, c.getNativeType(),
+ c.getColumnSize(), c.isNullable(), c.isIndexed(), t.getName(), c.getRemarks() }));
+ }
+ }
+ } else if ("relationships".equals(tableName)) {
+ // "relationships" columns: primary_table, primary_column,
+ // foreign_table, foreign_column
+ for (Relationship r : getMainSchemaInternal().getRelationships()) {
+ Column[] primaryColumns = r.getPrimaryColumns();
+ Column[] foreignColumns = r.getForeignColumns();
+ Table pTable = r.getPrimaryTable();
+ Table fTable = r.getForeignTable();
+ for (int i = 0; i < primaryColumns.length; i++) {
+ Column pColumn = primaryColumns[i];
+ Column fColumn = foreignColumns[i];
+ data.add(new DefaultRow(header, new Object[] { pTable.getName(), pColumn.getName(),
+ fTable.getName(), fColumn.getName() }));
+ }
+ }
+ } else {
+ throw new IllegalArgumentException("Cannot materialize non information_schema table: " + table);
+ }
+
+ DataSet dataSet;
+ if (data.isEmpty()) {
+ dataSet = new EmptyDataSet(selectItems);
+ } else {
+ dataSet = new InMemoryDataSet(header, data);
+ }
+
+ // Handle column subset
+ dataSet = MetaModelHelper.getSelection(selectItems, dataSet);
+
+ // Handle maxRows
+ if (maxRows != -1) {
+ dataSet = new MaxRowsDataSet(dataSet, maxRows);
+ }
+ return dataSet;
+ }
+
+ protected Schema getMainSchemaInternal() {
+ Schema schema = _mainSchema;
+ if (schema == null) {
+ schema = getMainSchema();
+ _mainSchema = schema;
+ }
+ return schema;
+ }
+
+ /**
+ * Adds a {@link TypeConverter} to this DataContext's query engine (Query
+ * Postprocessor) for read operations. Note that this method should NOT be
+ * invoked directly by consuming code. Rather use
+ * {@link Converters#addTypeConverter(DataContext, Column, TypeConverter)}
+ * to ensure conversion on both reads and writes.
+ */
+ @Override
+ public void addConverter(Column column, TypeConverter<?, ?> converter) {
+ _converters.put(column, converter);
+ }
+
+ /**
+ * @return the main schema that subclasses of this class produce
+ */
+ protected abstract Schema getMainSchema() throws MetaModelException;
+
+ /**
+ * @return the name of the main schema that subclasses of this class produce
+ */
+ protected abstract String getMainSchemaName() throws MetaModelException;
+
+ /**
+ * Executes a simple one-table query against a table in the main schema of
+ * the subclasses of this class. This default implementation will delegate
+ * to {@link #materializeMainSchemaTable(Table, Column[], int, int)}.
+ *
+ * @param table
+ * @param selectItems
+ * @param firstRow
+ * @param maxRows
+ * @return
+ */
+ protected DataSet materializeMainSchemaTable(Table table, List<SelectItem> selectItems, int firstRow, int maxRows) {
+ Column[] columns = new Column[selectItems.size()];
+ for (int i = 0; i < columns.length; i++) {
+ columns[i] = selectItems.get(i).getColumn();
+ }
+ DataSet dataSet = materializeMainSchemaTable(table, columns, firstRow, maxRows);
+
+ dataSet = MetaModelHelper.getSelection(selectItems, dataSet);
+
+ return dataSet;
+ }
+
+ /**
+ * Executes a simple one-table query against a table in the main schema of
+ * the subclasses of this class. This default implementation will delegate
+ * to {@link #materializeMainSchemaTable(Table, Column[], int)} and apply a
+ * {@link FirstRowDataSet} if necessary.
+ *
+ * @param table
+ * @param columns
+ * @param firstRow
+ * @param maxRows
+ * @return
+ */
+ protected DataSet materializeMainSchemaTable(Table table, Column[] columns, int firstRow, int maxRows) {
+ final int rowsToMaterialize;
+ if (firstRow == 1) {
+ rowsToMaterialize = maxRows;
+ } else {
+ rowsToMaterialize = maxRows + (firstRow - 1);
+ }
+ DataSet dataSet = materializeMainSchemaTable(table, columns, rowsToMaterialize);
+ if (firstRow > 1) {
+ dataSet = new FirstRowDataSet(dataSet, firstRow);
+ }
+ return dataSet;
+ }
+
+ /**
+ * Executes a simple one-table query against a table in the main schema of
+ * the subclasses of this class.
+ *
+ * @param table
+ * the table to query
+ * @param columns
+ * the columns of the table to query
+ * @param maxRows
+ * the maximum amount of rows needed or -1 if all rows are
+ * wanted.
+ * @return a dataset with the raw table/column content.
+ */
+ protected abstract DataSet materializeMainSchemaTable(Table table, Column[] columns, int maxRows);
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/QueryPostprocessDelegate.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/QueryPostprocessDelegate.java b/core/src/main/java/org/apache/metamodel/QueryPostprocessDelegate.java
new file mode 100644
index 0000000..7d8dde3
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/QueryPostprocessDelegate.java
@@ -0,0 +1,45 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel;
+
+import org.eobjects.metamodel.schema.Schema;
+
+/**
+ * A simple subclass of {@link QueryPostprocessDataContext} which provides less
+ * implementation fuzz when custom querying features (like composite
+ * datacontexts or type conversion) is needed.
+ *
+ * @author Kasper Sørensen
+ * @author Ankit Kumar
+ */
+public abstract class QueryPostprocessDelegate extends
+ QueryPostprocessDataContext {
+
+ @Override
+ protected String getMainSchemaName() throws MetaModelException {
+ throw new UnsupportedOperationException(
+ "QueryPostprocessDelegate cannot perform schema exploration");
+ }
+
+ @Override
+ protected Schema getMainSchema() throws MetaModelException {
+ throw new UnsupportedOperationException(
+ "QueryPostprocessDelegate cannot perform schema exploration");
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/SchemaNameComparator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/SchemaNameComparator.java b/core/src/main/java/org/apache/metamodel/SchemaNameComparator.java
new file mode 100644
index 0000000..14a2f82
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/SchemaNameComparator.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel;
+
+import java.util.Comparator;
+
+/**
+ * Comparator for comparing schema names.
+ *
+ * @author Kasper Sørensen
+ */
+class SchemaNameComparator implements Comparator<String> {
+
+ private static Comparator<? super String> _instance = new SchemaNameComparator();
+
+ public static Comparator<? super String> getInstance() {
+ return _instance;
+ }
+
+ private SchemaNameComparator() {
+ }
+
+ public int compare(String o1, String o2) {
+ if (o1 == null && o2 == null) {
+ return 0;
+ }
+ if (o1 == null) {
+ return -1;
+ }
+ if (o2 == null) {
+ return 1;
+ }
+ if (MetaModelHelper.isInformationSchema(o1)) {
+ return -1;
+ }
+ if (MetaModelHelper.isInformationSchema(o2)) {
+ return 1;
+ }
+ return o1.compareTo(o2);
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/UpdateCallback.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/UpdateCallback.java b/core/src/main/java/org/apache/metamodel/UpdateCallback.java
new file mode 100644
index 0000000..c19f03f
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/UpdateCallback.java
@@ -0,0 +1,45 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel;
+
+import org.eobjects.metamodel.create.TableCreatable;
+import org.eobjects.metamodel.delete.RowDeletable;
+import org.eobjects.metamodel.drop.TableDroppable;
+import org.eobjects.metamodel.insert.RowInsertable;
+import org.eobjects.metamodel.update.RowUpdateable;
+
+/**
+ * An {@link UpdateCallback} is used by an {@link UpdateScript} to perform
+ * updates on a {@link DataContext}. Multiple updates (eg. insertion of several
+ * rows or creation of multiple tables) can (and should) be performed with a
+ * single {@link UpdateCallback}. This pattern guarantees that connections
+ * and/or file handles are handled correctly, surrounding the
+ * {@link UpdateScript} that is being executed.
+ *
+ * @author Kasper Sørensen
+ */
+public interface UpdateCallback extends TableCreatable, TableDroppable, RowInsertable, RowUpdateable, RowDeletable {
+
+ /**
+ * Gets the DataContext on which the update script is being executed.
+ *
+ * @return the DataContext on which the update script is being executed.
+ */
+ public DataContext getDataContext();
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/UpdateScript.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/UpdateScript.java b/core/src/main/java/org/apache/metamodel/UpdateScript.java
new file mode 100644
index 0000000..b10318e
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/UpdateScript.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel;
+
+import org.eobjects.metamodel.util.Action;
+
+/**
+ * Represents any updating operation or update script that can be executed on a
+ * {@link UpdateableDataContext}. Users of MetaModel should implement their own
+ * {@link UpdateScript} and submit them to the
+ * {@link UpdateableDataContext#executeUpdate(UpdateScript)} method for
+ * execution.
+ *
+ * @author Kasper Sørensen
+ */
+public interface UpdateScript extends Action<UpdateCallback> {
+
+ /**
+ * Invoked by MetaModel when the update script should be run. User should
+ * implement this method and invoke update operations on the
+ * {@link UpdateCallback}.
+ */
+ @Override
+ public void run(UpdateCallback callback);
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/UpdateableDataContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/UpdateableDataContext.java b/core/src/main/java/org/apache/metamodel/UpdateableDataContext.java
new file mode 100644
index 0000000..3153f75
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/UpdateableDataContext.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel;
+
+/**
+ * Represents a {@link DataContext} that supports updating write-operations.
+ *
+ * @author Kasper Sørensen
+ */
+public interface UpdateableDataContext extends DataContext {
+
+ /**
+ * Submits an {@link UpdateScript} for execution on the {@link DataContext}.
+ *
+ * Since implementations of the {@link DataContext} vary quite a lot, there
+ * is no golden rule as to how an update script will be executed. But the
+ * implementors should strive towards handling an {@link UpdateScript} as a
+ * single transactional change to the data store.
+ *
+ * @param update
+ * the update script to execute
+ */
+ public void executeUpdate(UpdateScript update);
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/convert/ColumnTypeDetector.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/convert/ColumnTypeDetector.java b/core/src/main/java/org/apache/metamodel/convert/ColumnTypeDetector.java
new file mode 100644
index 0000000..1355c95
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/convert/ColumnTypeDetector.java
@@ -0,0 +1,82 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel.convert;
+
+import org.eobjects.metamodel.util.BooleanComparator;
+import org.eobjects.metamodel.util.TimeComparator;
+
+/**
+ * A class capable of detecting/narrowing a string column type to something more
+ * specific. Either: Boolean, Integer, Double or Date.
+ */
+final class ColumnTypeDetector {
+
+ private boolean _booleanPossible = true;
+ private boolean _integerPossible = true;
+ private boolean _doublePossible = true;
+ private boolean _datePossible = true;
+
+ public void registerValue(String stringValue) {
+ if (stringValue == null || stringValue.length() == 0) {
+ return;
+ }
+ if (_booleanPossible) {
+ try {
+ BooleanComparator.parseBoolean(stringValue);
+ } catch (IllegalArgumentException e) {
+ _booleanPossible = false;
+ }
+ }
+ if (_doublePossible) {
+ try {
+ Double.parseDouble(stringValue);
+ } catch (NumberFormatException e) {
+ _doublePossible = false;
+ _integerPossible = false;
+ }
+ // If integer is possible, double will always also be possible,
+ // but not nescesarily the other way around
+ if (_integerPossible) {
+ try {
+ Integer.parseInt(stringValue);
+ } catch (NumberFormatException e) {
+ _integerPossible = false;
+ }
+ }
+ }
+ if (_datePossible) {
+ if (TimeComparator.toDate(stringValue) == null) {
+ _datePossible = false;
+ }
+ }
+ }
+
+ public TypeConverter<?, ?> createConverter() {
+ if (_booleanPossible) {
+ return new StringToBooleanConverter();
+ } else if (_integerPossible) {
+ return new StringToIntegerConverter();
+ } else if (_doublePossible) {
+ return new StringToDoubleConverter();
+ } else if (_datePossible) {
+ return new StringToDateConverter();
+ }
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/convert/ConvertedDataSet.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/convert/ConvertedDataSet.java b/core/src/main/java/org/apache/metamodel/convert/ConvertedDataSet.java
new file mode 100644
index 0000000..ff0cf79
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/convert/ConvertedDataSet.java
@@ -0,0 +1,74 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel.convert;
+
+import org.eobjects.metamodel.data.AbstractDataSet;
+import org.eobjects.metamodel.data.DataSet;
+import org.eobjects.metamodel.data.DefaultRow;
+import org.eobjects.metamodel.data.Row;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link DataSet} wrapper/decorator which converts values using
+ * {@link TypeConverter}s before returning them to the user.
+ */
+final class ConvertedDataSet extends AbstractDataSet {
+
+ private static final Logger logger = LoggerFactory.getLogger(ConvertedDataSet.class);
+
+ private final DataSet _dataSet;
+ private final TypeConverter<?, ?>[] _converters;
+
+ public ConvertedDataSet(DataSet dataSet, TypeConverter<?, ?>[] converters) {
+ super(dataSet.getSelectItems());
+ _dataSet = dataSet;
+ _converters = converters;
+ }
+
+ @Override
+ public boolean next() {
+ return _dataSet.next();
+ }
+
+ @Override
+ public Row getRow() {
+ Row sourceRow = _dataSet.getRow();
+ Object[] values = new Object[_converters.length];
+ for (int i = 0; i < values.length; i++) {
+ Object value = sourceRow.getValue(i);
+
+ @SuppressWarnings("unchecked")
+ TypeConverter<Object, ?> converter = (TypeConverter<Object, ?>) _converters[i];
+
+ if (converter != null) {
+ Object virtualValue = converter.toVirtualValue(value);
+ logger.debug("Converted physical value {} to {}", value, virtualValue);
+ value = virtualValue;
+ }
+ values[i] = value;
+ }
+ return new DefaultRow(getHeader(), values);
+ }
+
+ @Override
+ public void close() {
+ _dataSet.close();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/convert/ConvertedDataSetInterceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/convert/ConvertedDataSetInterceptor.java b/core/src/main/java/org/apache/metamodel/convert/ConvertedDataSetInterceptor.java
new file mode 100644
index 0000000..159c469
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/convert/ConvertedDataSetInterceptor.java
@@ -0,0 +1,91 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel.convert;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eobjects.metamodel.data.DataSet;
+import org.eobjects.metamodel.intercept.DataSetInterceptor;
+import org.eobjects.metamodel.query.SelectItem;
+import org.eobjects.metamodel.schema.Column;
+
+/**
+ * A {@link DataSetInterceptor} used for intercepting values in {@link DataSet}s
+ * that need to be converted, according to a set of {@link TypeConverter}s.
+ *
+ * @see TypeConverter
+ * @see Converters
+ */
+public class ConvertedDataSetInterceptor implements DataSetInterceptor, HasReadTypeConverters {
+
+ private Map<Column, TypeConverter<?, ?>> _converters;
+
+ public ConvertedDataSetInterceptor() {
+ this(new HashMap<Column, TypeConverter<?, ?>>());
+ }
+
+ public ConvertedDataSetInterceptor(
+ Map<Column, TypeConverter<?, ?>> converters) {
+ _converters = converters;
+ }
+
+ @Override
+ public void addConverter(Column column, TypeConverter<?, ?> converter) {
+ if (converter == null) {
+ _converters.remove(column);
+ } else {
+ _converters.put(column, converter);
+ }
+ }
+
+ protected Map<Column, TypeConverter<?, ?>> getConverters(DataSet dataSet) {
+ return _converters;
+ }
+
+ @Override
+ public final DataSet intercept(DataSet dataSet) {
+ Map<Column, TypeConverter<?, ?>> converters = getConverters(dataSet);
+ if (converters.isEmpty()) {
+ return dataSet;
+ }
+
+ boolean hasConverter = false;
+ SelectItem[] selectItems = dataSet.getSelectItems();
+ TypeConverter<?, ?>[] converterArray = new TypeConverter[selectItems.length];
+ for (int i = 0; i < selectItems.length; i++) {
+ SelectItem selectItem = selectItems[i];
+ Column column = selectItem.getColumn();
+ if (column != null && selectItem.getFunction() == null) {
+ TypeConverter<?, ?> converter = converters.get(column);
+ if (converter != null) {
+ hasConverter = true;
+ converterArray[i] = converter;
+ }
+ }
+ }
+
+ if (!hasConverter) {
+ return dataSet;
+ }
+
+ return new ConvertedDataSet(dataSet, converterArray);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/convert/ConvertedRowInsertionInterceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/convert/ConvertedRowInsertionInterceptor.java b/core/src/main/java/org/apache/metamodel/convert/ConvertedRowInsertionInterceptor.java
new file mode 100644
index 0000000..b7ea3ad
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/convert/ConvertedRowInsertionInterceptor.java
@@ -0,0 +1,75 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel.convert;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eobjects.metamodel.insert.RowInsertionBuilder;
+import org.eobjects.metamodel.intercept.RowInsertionInterceptor;
+import org.eobjects.metamodel.schema.Column;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link RowInsertionInterceptor} used for intercepting values in
+ * {@link RowInsertionBuilder}s that need to be converted, according to a set of
+ * {@link TypeConverter}s.
+ *
+ * @see TypeConverter
+ * @see Converters
+ */
+public class ConvertedRowInsertionInterceptor implements RowInsertionInterceptor {
+
+ private static final Logger logger = LoggerFactory.getLogger(ConvertedRowInsertionInterceptor.class);
+
+ private final Map<Column, TypeConverter<?, ?>> _converters;
+
+ public ConvertedRowInsertionInterceptor() {
+ this(new HashMap<Column, TypeConverter<?, ?>>());
+ }
+
+ public ConvertedRowInsertionInterceptor(Map<Column, TypeConverter<?, ?>> converters) {
+ _converters = converters;
+ }
+
+ public void addConverter(Column column, TypeConverter<?, ?> converter) {
+ if (converter == null) {
+ _converters.remove(column);
+ } else {
+ _converters.put(column, converter);
+ }
+ }
+
+ @Override
+ public RowInsertionBuilder intercept(RowInsertionBuilder insert) {
+ if (_converters.isEmpty()) {
+ return insert;
+ }
+
+ logger.debug("Insert statement before conversion: {}", insert);
+
+ insert = Converters.convertRow(insert, _converters);
+
+ logger.debug("Insert statement after conversion: {}", insert);
+
+ return insert;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/convert/ConvertedRowUpdationInterceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/convert/ConvertedRowUpdationInterceptor.java b/core/src/main/java/org/apache/metamodel/convert/ConvertedRowUpdationInterceptor.java
new file mode 100644
index 0000000..6a42107
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/convert/ConvertedRowUpdationInterceptor.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel.convert;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eobjects.metamodel.intercept.RowUpdationInterceptor;
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.update.RowUpdationBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConvertedRowUpdationInterceptor implements RowUpdationInterceptor {
+
+ private static final Logger logger = LoggerFactory.getLogger(ConvertedRowUpdationInterceptor.class);
+
+ private final Map<Column, TypeConverter<?, ?>> _converters;
+
+ public ConvertedRowUpdationInterceptor() {
+ this(new HashMap<Column, TypeConverter<?, ?>>());
+ }
+
+ public ConvertedRowUpdationInterceptor(Map<Column, TypeConverter<?, ?>> converters) {
+ _converters = converters;
+ }
+
+ public void addConverter(Column column, TypeConverter<?, ?> converter) {
+ if (converter == null) {
+ _converters.remove(column);
+ } else {
+ _converters.put(column, converter);
+ }
+ }
+
+ @Override
+ public RowUpdationBuilder intercept(RowUpdationBuilder update) {
+ if (_converters.isEmpty()) {
+ return update;
+ }
+
+ logger.debug("Update statement after conversion: {}", update);
+
+ update = Converters.convertRow(update, _converters);
+
+ logger.debug("Update statement after conversion: {}", update);
+
+ return update;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/convert/Converters.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/convert/Converters.java b/core/src/main/java/org/apache/metamodel/convert/Converters.java
new file mode 100644
index 0000000..7b7b43c
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/convert/Converters.java
@@ -0,0 +1,329 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel.convert;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.eobjects.metamodel.DataContext;
+import org.eobjects.metamodel.MetaModelHelper;
+import org.eobjects.metamodel.UpdateableDataContext;
+import org.eobjects.metamodel.data.DataSet;
+import org.eobjects.metamodel.data.Row;
+import org.eobjects.metamodel.data.RowBuilder;
+import org.eobjects.metamodel.data.Style;
+import org.eobjects.metamodel.intercept.InterceptableDataContext;
+import org.eobjects.metamodel.intercept.Interceptors;
+import org.eobjects.metamodel.query.Query;
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.schema.SuperColumnType;
+import org.eobjects.metamodel.schema.Table;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class consists of static methods for decorating {@link DataContext}s
+ * with {@link TypeConverter}s, which allows for automatic conversion of values
+ * on data read and write operations.
+ */
+public final class Converters {
+
+ private static final Logger logger = LoggerFactory.getLogger(Converters.class);
+
+ private Converters() {
+ // prevent instantiation
+ }
+
+ /**
+ * Adds a type converter to a specific column in the {@link DataContext}.
+ *
+ * @param dataContext
+ * the DataContext to decorate
+ * @param column
+ * the column which holds values to convert
+ * @param converter
+ * the converter to use on the specified column
+ * @return a decorated DataContext, which should be used for successive
+ * operations on the data.
+ */
+ public static UpdateableDataContext addTypeConverter(UpdateableDataContext dataContext, Column column,
+ TypeConverter<?, ?> converter) {
+ return addTypeConverterInternally(dataContext, column, converter);
+ }
+
+ /**
+ * Adds a type converter to a specific column in the {@link DataContext}.
+ *
+ * @param dataContext
+ * the DataContext to decorate
+ * @param column
+ * the column which holds values to convert
+ * @param converter
+ * the converter to use on the specified column
+ * @return a decorated DataContext, which should be used for successive
+ * operations on the data.
+ */
+ public static DataContext addTypeConverter(DataContext dataContext, Column column, TypeConverter<?, ?> converter) {
+ return addTypeConverterInternally(dataContext, column, converter);
+ }
+
+ /**
+ * Adds a collection of type converters to specific columns in the
+ * {@link DataContext}
+ *
+ * @param dataContext
+ * the DataContext to decorate
+ * @param converters
+ * a map containing columns and mapped type converters.
+ * @return a decorated DataContext, which should be used for successive
+ * operations on the data.
+ */
+ public static UpdateableDataContext addTypeConverters(UpdateableDataContext dataContext,
+ Map<Column, TypeConverter<?, ?>> converters) {
+ return addTypeConvertersInternally(dataContext, converters);
+ }
+
+ /**
+ * Adds a collection of type converters to specific columns in the
+ * {@link DataContext}
+ *
+ * @param dataContext
+ * the DataContext to decorate
+ * @param converters
+ * a map containing columns and mapped type converters.
+ * @return a decorated DataContext, which should be used for successive
+ * operations on the data.
+ */
+ public static DataContext addTypeConverters(DataContext dataContext, Map<Column, TypeConverter<?, ?>> converters) {
+ return addTypeConvertersInternally(dataContext, converters);
+ }
+
+ /**
+ * Auto-detects / guesses the type converters to be applied on set of
+ * columns. This method will query the String columns in order to assert
+ * which columns are likely candidates for conversion.
+ *
+ * As such this method is not guaranteed to pick the correct converters,
+ * since data can change over time or other conversions can be requested.
+ *
+ * @param dataContext
+ * the DataContext that holds the data.
+ * @param columns
+ * the columns to inspect to find type conversion candidates.
+ * @param sampleSize
+ * the max amount of rows to query for doing auto-detection. Use
+ * {@link Integer#MAX_VALUE} if no constraint should be put on
+ * the number of records to sample.
+ * @return a map of {@link Column}s and {@link TypeConverter}s which can be
+ * used (eg. with the {@link #addTypeConverters(DataContext, Map)}
+ * method) to decorate the DataContext with type converters.
+ */
+ public static Map<Column, TypeConverter<?, ?>> autoDetectConverters(DataContext dataContext, Column[] columns,
+ int sampleSize) {
+ columns = MetaModelHelper.getColumnsBySuperType(columns, SuperColumnType.LITERAL_TYPE);
+ final Map<Column, TypeConverter<?, ?>> result = new HashMap<Column, TypeConverter<?, ?>>();
+ Table[] tables = MetaModelHelper.getTables(columns);
+ for (Table table : tables) {
+ Column[] tableColumns = MetaModelHelper.getTableColumns(table, columns);
+ autoDetectConvertersInternally(dataContext, table, tableColumns, sampleSize, result);
+ }
+ return result;
+ }
+
+ /**
+ * Auto-detects / guesses the type converters to be applied on a table. This
+ * method will query the String columns of a table in order to assert which
+ * columns are likely candidates for conversion.
+ *
+ * As such this method is not guaranteed to pick the correct converters,
+ * since data can change over time or other conversions can be requested.
+ *
+ * @param dataContext
+ * the DataContext that holds the data.
+ * @param table
+ * the table to inspect to find type conversion candidates. This
+ * table will hold all columns of the result.
+ * @param sampleSize
+ * the max amount of rows to query for doing auto-detection. Use
+ * {@link Integer#MAX_VALUE} if no constraint should be put on
+ * the number of records to sample.
+ * @return a map of {@link Column}s and {@link TypeConverter}s which can be
+ * used (eg. with the {@link #addTypeConverters(DataContext, Map)}
+ * method) to decorate the DataContext with type converters.
+ */
+ public static Map<Column, TypeConverter<?, ?>> autoDetectConverters(DataContext dataContext, Table table,
+ int sampleSize) {
+ final Map<Column, TypeConverter<?, ?>> result = new HashMap<Column, TypeConverter<?, ?>>();
+ Column[] columns = table.getColumnsOfSuperType(SuperColumnType.LITERAL_TYPE);
+ autoDetectConvertersInternally(dataContext, table, columns, sampleSize, result);
+ return result;
+ }
+
+ private static void autoDetectConvertersInternally(DataContext dataContext, Table table, Column[] columns,
+ int sampleSize, Map<Column, TypeConverter<?, ?>> result) {
+ if (columns == null || columns.length == 0) {
+ return;
+ }
+
+ Map<Column, ColumnTypeDetector> detectors = new HashMap<Column, ColumnTypeDetector>();
+ for (Column column : columns) {
+ detectors.put(column, new ColumnTypeDetector());
+ }
+
+ Query query = dataContext.query().from(table).select(columns).toQuery();
+ if (sampleSize > 0 && sampleSize != Integer.MAX_VALUE) {
+ query.setMaxRows(sampleSize);
+ }
+ DataSet dataSet = dataContext.executeQuery(query);
+ try {
+ while (dataSet.next()) {
+ Row row = dataSet.getRow();
+ for (Column column : columns) {
+ String stringValue = (String) row.getValue(column);
+ ColumnTypeDetector detector = detectors.get(column);
+ detector.registerValue(stringValue);
+ }
+ }
+ } finally {
+ dataSet.close();
+ }
+ for (Column column : columns) {
+ ColumnTypeDetector detector = detectors.get(column);
+ TypeConverter<?, ?> converter = detector.createConverter();
+ if (converter != null) {
+ result.put(column, converter);
+ }
+ }
+ }
+
+ private static InterceptableDataContext addTypeConvertersInternally(final DataContext dc,
+ Map<Column, TypeConverter<?, ?>> converters) {
+ if (converters == null) {
+ throw new IllegalArgumentException("Converters cannot be null");
+ }
+
+ InterceptableDataContext interceptable = Interceptors.intercept(dc);
+
+ Set<Entry<Column, TypeConverter<?, ?>>> entries = converters.entrySet();
+ for (Entry<Column, TypeConverter<?, ?>> entry : entries) {
+ Column column = entry.getKey();
+ TypeConverter<?, ?> converter = entry.getValue();
+ interceptable = addTypeConverterInternally(interceptable, column, converter);
+ }
+
+ return interceptable;
+ }
+
+ private static InterceptableDataContext addTypeConverterInternally(final DataContext dc, Column column,
+ TypeConverter<?, ?> converter) {
+ if (column == null) {
+ throw new IllegalArgumentException("Column cannot be null");
+ }
+
+ InterceptableDataContext interceptable = Interceptors.intercept(dc);
+ DataContext delegate = interceptable.getDelegate();
+
+ boolean interceptDataSets = true;
+
+ if (delegate instanceof HasReadTypeConverters) {
+ // some DataContexts implement the HasTypeConverters interface,
+ // which is preferred when available
+ HasReadTypeConverters hasTypeConverter = (HasReadTypeConverters) delegate;
+ hasTypeConverter.addConverter(column, converter);
+
+ interceptDataSets = false;
+ }
+
+ addTypeConverterInterceptors(interceptable, column, converter, interceptDataSets);
+ return interceptable;
+ }
+
+ private static void addTypeConverterInterceptors(InterceptableDataContext interceptable, Column column,
+ TypeConverter<?, ?> converter, boolean interceptDataSets) {
+ // intercept datasets (reads)
+ if (interceptDataSets) {
+ ConvertedDataSetInterceptor interceptor = interceptable.getDataSetInterceptors().getInterceptorOfType(
+ ConvertedDataSetInterceptor.class);
+ if (interceptor == null) {
+ interceptor = new ConvertedDataSetInterceptor();
+ interceptable.addDataSetInterceptor(interceptor);
+ }
+ interceptor.addConverter(column, converter);
+ }
+
+ // intercept inserts (writes)
+ {
+ ConvertedRowInsertionInterceptor interceptor = interceptable.getRowInsertionInterceptors()
+ .getInterceptorOfType(ConvertedRowInsertionInterceptor.class);
+ if (interceptor == null) {
+ interceptor = new ConvertedRowInsertionInterceptor();
+ interceptable.addRowInsertionInterceptor(interceptor);
+ }
+ interceptor.addConverter(column, converter);
+ }
+
+ // convert updates
+ {
+ ConvertedRowUpdationInterceptor interceptor = interceptable.getRowUpdationInterceptors()
+ .getInterceptorOfType(ConvertedRowUpdationInterceptor.class);
+ if (interceptor == null) {
+ interceptor = new ConvertedRowUpdationInterceptor();
+ interceptable.addRowUpdationInterceptor(interceptor);
+ }
+ interceptor.addConverter(column, converter);
+ }
+
+ // converting deletes (as well as where-items in updates) should not be
+ // applied, because the DataSet interceptor is anyways only working on
+ // the output. In that sense it adds symetry to NOT support conversion
+ // in the where clause of UPDATEs and DELETEs.
+ }
+
+ /**
+ * Converts values in a {@link RowBuilder}.
+ *
+ * @param rowBuilder
+ * @param converters
+ * @return
+ */
+ protected static <RB extends RowBuilder<?>> RB convertRow(RB rowBuilder, Map<Column, TypeConverter<?, ?>> converters) {
+ Table table = rowBuilder.getTable();
+ Column[] columns = table.getColumns();
+ Row row = rowBuilder.toRow();
+ for (Column column : columns) {
+ @SuppressWarnings("unchecked")
+ TypeConverter<?, Object> converter = (TypeConverter<?, Object>) converters.get(column);
+ if (converter != null) {
+ final int indexInRow = row.indexOf(column);
+ final Object value = row.getValue(indexInRow);
+ final Object physicalValue = converter.toPhysicalValue(value);
+ logger.debug("Converted virtual value {} to {}", value, physicalValue);
+ if (value == null && physicalValue == null && !rowBuilder.isSet(column)) {
+ logger.debug("Omitting implicit null value for column: {}", column);
+ } else {
+ final Style style = row.getStyle(indexInRow);
+ rowBuilder.value(column, physicalValue, style);
+ }
+ }
+ }
+ return rowBuilder;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/convert/HasReadTypeConverters.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/convert/HasReadTypeConverters.java b/core/src/main/java/org/apache/metamodel/convert/HasReadTypeConverters.java
new file mode 100644
index 0000000..5f63c1e
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/convert/HasReadTypeConverters.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel.convert;
+
+import org.eobjects.metamodel.data.DataSet;
+import org.eobjects.metamodel.schema.Column;
+
+/**
+ * Defines an interface for objects that are aware of {@link TypeConverter}s,
+ * and know how to apply them to read operations (Queries or {@link DataSet}s).
+ *
+ * @author Kasper Sørensen
+ */
+public interface HasReadTypeConverters {
+
+ public void addConverter(Column column, TypeConverter<?, ?> converter);
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/convert/StringToBooleanConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/convert/StringToBooleanConverter.java b/core/src/main/java/org/apache/metamodel/convert/StringToBooleanConverter.java
new file mode 100644
index 0000000..fb546a4
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/convert/StringToBooleanConverter.java
@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel.convert;
+
+import org.eobjects.metamodel.util.BooleanComparator;
+
+/**
+ * A {@link TypeConverter} that converts String values (on the physical layer)
+ * to interpreted Booleans.
+ *
+ * @author Kasper Sørensen
+ * @author Ankit Kumar
+ */
+public class StringToBooleanConverter implements TypeConverter<String, Boolean> {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toPhysicalValue(Boolean virtualValue) {
+ if (virtualValue == null) {
+ return null;
+ }
+ return virtualValue.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Boolean toVirtualValue(String physicalValue) {
+ if (physicalValue == null || physicalValue.length() == 0) {
+ return null;
+ }
+ return BooleanComparator.parseBoolean(physicalValue);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/convert/StringToDateConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/convert/StringToDateConverter.java b/core/src/main/java/org/apache/metamodel/convert/StringToDateConverter.java
new file mode 100644
index 0000000..1684a31
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/convert/StringToDateConverter.java
@@ -0,0 +1,127 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel.convert;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.eobjects.metamodel.util.Func;
+import org.eobjects.metamodel.util.TimeComparator;
+
+/**
+ * A {@link TypeConverter} that converts String values (on the physical layer)
+ * to interpreted {@link Date}s.
+ *
+ * @author Kasper Sørensen
+ * @author Ankit Kumar
+ */
+public class StringToDateConverter implements TypeConverter<String, Date> {
+
+ private final Func<Date, String> _serializeFunc;
+ private final Func<String, Date> _deserializeFunc;
+
+ /**
+ * Constructs a new {@link StringToDateConverter} which will use the
+ * {@link TimeComparator#toDate(Object)} method for parsing dates and the
+ * {@link DateFormat#MEDIUM} date time format for physical representation.
+ */
+ public StringToDateConverter() {
+ _deserializeFunc = new Func<String, Date>() {
+ @Override
+ public Date eval(String stringValue) {
+ return TimeComparator.toDate(stringValue);
+ }
+ };
+ _serializeFunc = new Func<Date, String>() {
+ @Override
+ public String eval(Date date) {
+ return DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
+ DateFormat.MEDIUM).format(date);
+ }
+ };
+ }
+
+ /**
+ * Constructs a new {@link StringToDateConverter} using a given date
+ * pattern.
+ *
+ * @param datePattern
+ * a String date pattern, corresponding to the syntax of a
+ * {@link SimpleDateFormat}.
+ */
+ public StringToDateConverter(String datePattern) {
+ this(new SimpleDateFormat(datePattern));
+ }
+
+ /**
+ * Constructs a new {@link StringToDateConverter} using a given
+ * {@link DateFormat}.
+ *
+ * @param dateFormat
+ * the {@link DateFormat} to use for parsing and formatting
+ * dates.
+ */
+ public StringToDateConverter(final DateFormat dateFormat) {
+ if (dateFormat == null) {
+ throw new IllegalArgumentException("DateFormat cannot be null");
+ }
+ _deserializeFunc = new Func<String, Date>() {
+ @Override
+ public Date eval(String string) {
+ try {
+ return dateFormat.parse(string);
+ } catch (ParseException e) {
+ throw new IllegalArgumentException(
+ "Could not parse date string: " + string);
+ }
+ }
+ };
+ _serializeFunc = new Func<Date, String>() {
+ @Override
+ public String eval(Date date) {
+ return dateFormat.format(date);
+ }
+ };
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toPhysicalValue(Date virtualValue) {
+ if (virtualValue == null) {
+ return null;
+ }
+ return _serializeFunc.eval(virtualValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Date toVirtualValue(String physicalValue) {
+ if (physicalValue == null || physicalValue.length() == 0) {
+ return null;
+ }
+ return _deserializeFunc.eval(physicalValue);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/convert/StringToDoubleConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/convert/StringToDoubleConverter.java b/core/src/main/java/org/apache/metamodel/convert/StringToDoubleConverter.java
new file mode 100644
index 0000000..6ce6406
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/convert/StringToDoubleConverter.java
@@ -0,0 +1,52 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel.convert;
+
+/**
+ * A {@link TypeConverter} that converts String values (on the physical layer)
+ * to interpreted Doubles.
+ *
+ * @author Kasper Sørensen
+ * @author Ankit Kumar
+ */
+public class StringToDoubleConverter implements TypeConverter<String, Double> {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toPhysicalValue(Double virtualValue) {
+ if (virtualValue == null) {
+ return null;
+ }
+ return virtualValue.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Double toVirtualValue(String physicalValue) {
+ if (physicalValue == null || physicalValue.length() == 0) {
+ return null;
+ }
+ return Double.parseDouble(physicalValue);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/convert/StringToIntegerConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/convert/StringToIntegerConverter.java b/core/src/main/java/org/apache/metamodel/convert/StringToIntegerConverter.java
new file mode 100644
index 0000000..4aafe7d
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/convert/StringToIntegerConverter.java
@@ -0,0 +1,52 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel.convert;
+
+/**
+ * A {@link TypeConverter} that converts String values (on the physical layer)
+ * to interpreted Integers.
+ *
+ * @author Kasper Sørensen
+ * @author Ankit Kumar
+ */
+public class StringToIntegerConverter implements TypeConverter<String, Integer> {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toPhysicalValue(Integer virtualValue) {
+ if (virtualValue == null) {
+ return null;
+ }
+ return virtualValue.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Integer toVirtualValue(String physicalValue) {
+ if (physicalValue == null || physicalValue.length() == 0) {
+ return null;
+ }
+ return Integer.parseInt(physicalValue);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/convert/TypeConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/convert/TypeConverter.java b/core/src/main/java/org/apache/metamodel/convert/TypeConverter.java
new file mode 100644
index 0000000..542449e
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/convert/TypeConverter.java
@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel.convert;
+
+/**
+ * Defines an interface for converting values from and to their physical
+ * materializations and their virtual representations.
+ *
+ * @see ConvertedDataContext
+ *
+ * @author Kasper Sørensen
+ * @author Ankit Kumar
+ *
+ * @param <P>
+ * the physical type of value
+ * @param <V>
+ * the virtual type of value
+ */
+public interface TypeConverter<P, V> {
+
+ /**
+ * Converts a virtual representation of a value into it's physical value.
+ *
+ * @param virtualValue
+ * the virtual representation
+ * @return the physical value
+ */
+ public P toPhysicalValue(V virtualValue);
+
+ /**
+ * Converts a physical value into it's virtual representation.
+ *
+ * @param physicalValue
+ * the physical value
+ * @return the virtual representation
+ */
+ public V toVirtualValue(P physicalValue);
+}
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/convert/package-info.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/convert/package-info.java b/core/src/main/java/org/apache/metamodel/convert/package-info.java
new file mode 100644
index 0000000..0adf2aa
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/convert/package-info.java
@@ -0,0 +1,23 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+/**
+ * DataContext decorator for implicit conversion of value types after querying and before insertion.
+ */
+package org.eobjects.metamodel.convert;
+
http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/create/AbstractColumnBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/create/AbstractColumnBuilder.java b/core/src/main/java/org/apache/metamodel/create/AbstractColumnBuilder.java
new file mode 100644
index 0000000..26fee26
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/create/AbstractColumnBuilder.java
@@ -0,0 +1,87 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.eobjects.metamodel.create;
+
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.schema.ColumnType;
+import org.eobjects.metamodel.schema.MutableColumn;
+
+/**
+ * Convenience implementation of all {@link ColumnBuilder} methods
+ *
+ * @param <T>
+ * the return type of the builder methods.
+ */
+abstract class AbstractColumnBuilder<T extends ColumnBuilder<?>> implements ColumnBuilder<T> {
+
+ private final MutableColumn _column;
+
+ public AbstractColumnBuilder(MutableColumn column) {
+ _column = column;
+ }
+
+ protected MutableColumn getColumn() {
+ return _column;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected T getReturnObject() {
+ return (T) this;
+ }
+
+ @Override
+ public final T like(Column column) {
+ _column.setColumnSize(column.getColumnSize());
+ _column.setNativeType(column.getNativeType());
+ _column.setType(column.getType());
+ _column.setNullable(column.isNullable());
+ return getReturnObject();
+ }
+
+ @Override
+ public final T ofType(ColumnType type) {
+ _column.setType(type);
+ return getReturnObject();
+ }
+
+ @Override
+ public final T ofNativeType(String nativeType) {
+ _column.setNativeType(nativeType);
+ return getReturnObject();
+ }
+
+ @Override
+ public final T ofSize(int size) {
+ _column.setColumnSize(size);
+ return getReturnObject();
+ }
+
+ @Override
+ public final T nullable(boolean nullable) {
+ _column.setNullable(nullable);
+ return getReturnObject();
+ }
+
+ @Override
+ public final T asPrimaryKey() {
+ _column.setPrimaryKey(true);
+ return getReturnObject();
+ }
+
+}