http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/9cf45b43/modules/schema-import/src/main/java/org/apache/ignite/schema/model/PojoDescriptor.java ---------------------------------------------------------------------- diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/model/PojoDescriptor.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/model/PojoDescriptor.java new file mode 100644 index 0000000..9f3322b --- /dev/null +++ b/modules/schema-import/src/main/java/org/apache/ignite/schema/model/PojoDescriptor.java @@ -0,0 +1,510 @@ +/* + * 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.apache.ignite.schema.model; + +import javafx.beans.property.*; +import javafx.beans.value.*; +import javafx.collections.*; +import org.apache.ignite.lang.*; +import org.apache.ignite.schema.parser.*; + +import java.math.*; +import java.util.*; + +import static java.sql.Types.*; + +/** + * Descriptor for java type. + */ +public class PojoDescriptor { + /** Database table. */ + private final DbTable tbl; + + /** Selected property. */ + private final BooleanProperty useProp; + + /** Previous name for key class. */ + private final String keyClsNamePrev; + + /** Key class name to show on screen. */ + private final StringProperty keyClsNameProp; + + /** Previous name for value class. */ + private final String valClsNamePrev; + + /** Value class name to show on screen. */ + private final StringProperty valClsNameProp; + + /** Parent item (schema name). */ + private final PojoDescriptor parent; + + /** Children items (tables names). */ + private Collection<PojoDescriptor> children = Collections.emptyList(); + + /** Indeterminate state of parent. */ + private final BooleanProperty indeterminateProp = new SimpleBooleanProperty(false); + + /** Full database name: schema + table. */ + private final String fullDbName; + + /** Java class fields. */ + private final ObservableList<PojoField> fields; + + /** Fields map for quick access. */ + private final Map<String, PojoField> fieldsMap; + + /** + * Constructor of POJO descriptor. + * + * @param prn Parent descriptor. + * @param tbl Database table Tab;e. + */ + public PojoDescriptor(PojoDescriptor prn, DbTable tbl) { + parent = prn; + + this.tbl = tbl; + + fullDbName = tbl.schema() + "." + tbl.table(); + + valClsNamePrev = toJavaClassName(tbl.table()); + valClsNameProp = new SimpleStringProperty(valClsNamePrev); + + keyClsNamePrev = valClsNamePrev.isEmpty() ? "" : valClsNamePrev + "Key"; + keyClsNameProp = new SimpleStringProperty(keyClsNamePrev); + + Collection<DbColumn> cols = tbl.columns(); + + List<PojoField> flds = new ArrayList<>(cols.size()); + + fieldsMap = new HashMap<>(cols.size()); + + for (DbColumn col : cols) { + String colName = col.name(); + + PojoField fld = new PojoField(colName, col.type(), + toJavaFieldName(colName), toJavaType(col.type(), col.nullable()).getName(), + col.key(), col.nullable()); + + fld.owner(this); + + flds.add(fld); + + fieldsMap.put(colName, fld); + } + + fields = FXCollections.observableList(flds); + + boolean isTbl = parent != null; + + boolean hasKeys = !isTbl || !keyFields().isEmpty(); + + useProp = new SimpleBooleanProperty(hasKeys); + + if (isTbl && !hasKeys && !parent.indeterminateProp.get()) + parent.indeterminateProp.set(true); + + useProp.addListener(new ChangeListener<Boolean>() { + @Override public void changed(ObservableValue<? extends Boolean> val, Boolean oldVal, Boolean newVal) { + for (PojoDescriptor child : children) + child.useProp.set(newVal); + + if (parent != null && !parent.children.isEmpty()) { + Iterator<PojoDescriptor> it = parent.children.iterator(); + + boolean parentIndeterminate = false; + boolean first = it.next().useProp.get(); + + while (it.hasNext()) { + if (it.next().useProp.get() != first) { + parentIndeterminate = true; + + break; + } + } + + parent.indeterminateProp.set(parentIndeterminate); + + if (!parentIndeterminate) + parent.useProp.set(first); + } + } + }); + } + + /** + * @return Parent descriptor. + */ + public PojoDescriptor parent() { + return parent; + } + + /** + * @return Full database name: schema + table. + */ + public String fullDbName() { + return fullDbName; + } + + /** + * @return {@code true} if POJO descriptor is a table descriptor and checked in GUI. + */ + public boolean checked() { + return parent != null && useProp.get(); + } + + /** + * @return Boolean property support for {@code use} property. + */ + public BooleanProperty useProperty() { + return useProp; + } + + /** + * @return Boolean property support for parent {@code indeterminate} property. + */ + public BooleanProperty indeterminate() { + return indeterminateProp; + } + + /** + * @return Key class name. + */ + public String keyClassName() { + return keyClsNameProp.get(); + } + + /** + * @param name New key class name. + */ + public void keyClassName(String name) { + keyClsNameProp.set(name); + } + + /** + * @return Value class name. + */ + public String valueClassName() { + return valClsNameProp.get(); + } + + /** + * @param name New value class name. + */ + public void valueClassName(String name) { + valClsNameProp.set(name); + } + + /** + * @return {@code true} if at least one field checked as "used". + */ + public boolean hasFields() { + for (PojoField field : fields) + if (field.use()) + return true; + + return false; + } + + /** + * @return {@code true} if at least one field checked as "used" and checked as "key". + */ + public boolean hasKeyFields() { + for (PojoField field : fields) + if (field.use() && field.key()) + return true; + + return false; + } + + /** + * @param includeKeys {@code true} if key fields should be included into value class. + * @return {@code true} if at least one field checked as "used" and not checked as "key". + */ + public boolean hasValueFields(boolean includeKeys) { + if (includeKeys) + return hasKeyFields(); + + for (PojoField field : fields) + if (field.use() && !field.key()) + return true; + + return false; + } + + /** + * @return Collection of key fields. + */ + public Collection<PojoField> keyFields() { + Collection<PojoField> keys = new ArrayList<>(); + + for (PojoField field : fields) + if (field.use() && field.key() ) + keys.add(field); + + return keys; + } + + /** + * @param includeKeys {@code true} if key fields should be included into value class. + * @return Collection of value fields. + */ + public Collection<PojoField> valueFields(boolean includeKeys) { + Collection<PojoField> vals = new ArrayList<>(); + + for (PojoField field : fields) + if (field.use() && (includeKeys || !field.key())) + vals.add(field); + + return vals; + } + + /** + * @return Ascending fields. + */ + public Collection<PojoField> ascendingFields() { + Collection<PojoField> res = new ArrayList<>(); + + Set<String> asc = tbl.ascendingColumns(); + + for (PojoField field : fields) + if (field.use() && asc.contains(field.dbName())) + res.add(field); + + return res; + } + + /** + * @return Descending fields. + */ + public Collection<PojoField> descendingFields() { + Collection<PojoField> res = new ArrayList<>(); + + Set<String> desc = tbl.descendingColumns(); + + for (PojoField field : fields) + if (field.use() && desc.contains(field.dbName())) + res.add(field); + + return res; + } + + /** + * Gets indexes groups. + */ + public Map<String, Map<String, IgniteBiTuple<String, Boolean>>> groups() { + Map<String, Map<String, Boolean>> idxs = tbl.indexes(); + + Map<String, Map<String, IgniteBiTuple<String, Boolean>>> groups = new LinkedHashMap<>(idxs.size()); + + for (Map.Entry<String, Map<String, Boolean>> idx : idxs.entrySet()) { + String idxName = idx.getKey(); + + Map<String, Boolean> idxCols = idx.getValue(); + + Map<String, IgniteBiTuple<String, Boolean>> grp = new LinkedHashMap<>(); + + groups.put(idxName, grp); + + for (Map.Entry<String, Boolean> idxCol : idxCols.entrySet()) { + PojoField fld = fieldsMap.get(idxCol.getKey()); + + grp.put(fld.javaName(), new IgniteBiTuple<>(fld.javaTypeName(), idxCol.getValue())); + } + } + + return groups; + } + + /** + * @return Key class name property. + */ + public StringProperty keyClassNameProperty() { + return keyClsNameProp; + } + + /** + * @return Value class name property. + */ + public StringProperty valueClassNameProperty() { + return valClsNameProp; + } + + /** + * @return Schema name. + */ + public String schema() { + return tbl.schema(); + } + + /** + * @return Table name. + */ + public String table() { + return tbl.table(); + } + + /** + * Sets children items. + * + * @param children Items to set. + */ + public void children(Collection<PojoDescriptor> children) { + this.children = children; + } + + /** + * @return {@code true} if descriptor was changed by user via GUI. + */ + public boolean changed() { + if (!keyClsNameProp.get().equals(keyClsNamePrev) || !valClsNameProp.get().equals(valClsNamePrev)) + return true; + + for (PojoField field : fields) + if (field.changed()) + return true; + + return false; + } + + /** + * Revert changes to key class name made by user. + */ + public void revertKeyClassName() { + keyClsNameProp.set(keyClsNamePrev); + } + + /** + * Revert changes to value class name made by user. + */ + public void revertValueClassName() { + valClsNameProp.set(valClsNamePrev); + } + + /** + * Revert changes to java names made by user. + */ + public void revertJavaNames() { + for (PojoField field : fields) + field.resetJavaName(); + } + + /** + * @return Java class fields. + */ + public ObservableList<PojoField> fields() { + return fields; + } + + /** + * @param name Source name. + * @return String converted to java class name notation. + */ + private static String toJavaClassName(String name) { + int len = name.length(); + + StringBuilder buf = new StringBuilder(len); + + boolean capitalizeNext = true; + + for (int i = 0; i < len; i++) { + char ch = name.charAt(i); + + if (Character.isWhitespace(ch) || '_' == ch) + capitalizeNext = true; + else if (capitalizeNext) { + buf.append(Character.toUpperCase(ch)); + + capitalizeNext = false; + } + else + buf.append(Character.toLowerCase(ch)); + } + + return buf.toString(); + } + + /** + * @param name Source name. + * @return String converted to java field name notation. + */ + private static String toJavaFieldName(String name) { + String javaName = toJavaClassName(name); + + return Character.toLowerCase(javaName.charAt(0)) + javaName.substring(1); + } + + /** + * Convert JDBC data type to java type. + * + * @param type JDBC SQL data type. + * @param nullable {@code true} if {@code NULL} is allowed for this field in database. + * @return Java data type. + */ + private static Class<?> toJavaType(int type, boolean nullable) { + switch (type) { + case BIT: + case BOOLEAN: + return nullable ? Boolean.class : boolean.class; + + case TINYINT: + return nullable ? Byte.class : byte.class; + + case SMALLINT: + return nullable ? Short.class : short.class; + + case INTEGER: + return nullable ? Integer.class : int.class; + + case BIGINT: + return nullable ? Long.class : long.class; + + case REAL: + return nullable ? Float.class : float.class; + + case FLOAT: + case DOUBLE: + return nullable ? Double.class : double.class; + + case NUMERIC: + case DECIMAL: + return BigDecimal.class; + + case CHAR: + case VARCHAR: + case LONGVARCHAR: + case NCHAR: + case NVARCHAR: + case LONGNVARCHAR: + return String.class; + + case DATE: + return java.sql.Date.class; + + case TIME: + return java.sql.Time.class; + + case TIMESTAMP: + return java.sql.Timestamp.class; + + // BINARY, VARBINARY, LONGVARBINARY, ARRAY, BLOB, CLOB, NCLOB, NULL, DATALINK + // OTHER, JAVA_OBJECT, DISTINCT, STRUCT, REF, ROWID, SQLXML + default: + return Object.class; + } + } +}
http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/9cf45b43/modules/schema-import/src/main/java/org/apache/ignite/schema/model/PojoField.java ---------------------------------------------------------------------- diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/model/PojoField.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/model/PojoField.java new file mode 100644 index 0000000..10939d9 --- /dev/null +++ b/modules/schema-import/src/main/java/org/apache/ignite/schema/model/PojoField.java @@ -0,0 +1,420 @@ +/* + * 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.apache.ignite.schema.model; + +import javafx.beans.property.*; +import javafx.beans.value.*; +import javafx.collections.*; + +import java.math.*; +import java.util.*; + +import static java.sql.Types.*; + +/** + * Field descriptor with properties for JavaFX GUI bindings. + */ +public class PojoField { + /** If this field should be used for code generation. */ + private final BooleanProperty useProp; + + /** If this field belongs to primary key. */ + private final BooleanProperty keyProp; + + /** If this field is an affinity key. */ + private final BooleanProperty akProp; + + /** If this field initially belongs to primary key. */ + private final boolean keyPrev; + + /** Field name in database. */ + private final StringProperty dbNameProp; + + /** Field type in database. */ + private final StringProperty dbTypeNameProp; + + /** Field name in POJO. */ + private final StringProperty javaNameProp; + + /** Initial field name in POJO. */ + private final String javaNamePrev; + + /** Field type in POJO. */ + private final StringProperty javaTypeNameProp; + + /** Initial field type in POJO. */ + private final String javaTypeNamePrev; + + /** Is {@code NULL} allowed for field in database. */ + private final boolean nullable; + + /** List of possible java type conversions. */ + private final ObservableList<String> conversions; + + /** Field owner. */ + private PojoDescriptor owner; + + /** + * @param clss List of classes to get class names. + * @return List of classes names to show in UI for manual select. + */ + private static List<String> classNames(Class<?>... clss) { + List<String> names = new ArrayList<>(clss.length); + + for (Class<?> cls : clss) + names.add(cls.getName()); + + return names; + } + + /** Null number conversions. */ + private static final ObservableList<String> NULL_NUM_CONVERSIONS = FXCollections.observableArrayList(); + + /** Not null number conversions. */ + private static final ObservableList<String> NOT_NULL_NUM_CONVERSIONS = FXCollections.observableArrayList(); + + /** Primitive types. */ + private static final List<String> PRIMITIVES = classNames(boolean.class, byte.class, short.class, + int.class, long.class, float.class, double.class); + + /** Object types. */ + private static final List<String> OBJECTS = classNames(Boolean.class, Byte.class, Short.class, Integer.class, + Long.class, Float.class, Double.class, BigDecimal.class); + + static { + NOT_NULL_NUM_CONVERSIONS.addAll(PRIMITIVES); + NOT_NULL_NUM_CONVERSIONS.addAll(OBJECTS); + + NULL_NUM_CONVERSIONS.addAll(OBJECTS); + } + + /** + * @param dbType Database type. + * @param nullable Nullable. + * @param dflt Default. + * @return List of possible type conversions. + */ + private static ObservableList<String> conversions(int dbType, boolean nullable, String dflt) { + switch (dbType) { + case TINYINT: + case SMALLINT: + case INTEGER: + case BIGINT: + case REAL: + case FLOAT: + case DOUBLE: + return nullable ? NULL_NUM_CONVERSIONS : NOT_NULL_NUM_CONVERSIONS; + + default: + return FXCollections.singletonObservableList(dflt); + } + } + + /** + * @param dbName Field name in database. + * @param dbType Field JDBC type in database. + * @param javaName Field name in POJO. + * @param javaTypeName Field type in POJO. + * @param key {@code true} if this field belongs to primary key. + * @param nullable {@code true} if {@code NULL} allowed for field in database. + */ + public PojoField(String dbName, int dbType, String javaName, String javaTypeName, boolean key, boolean nullable) { + dbNameProp = new SimpleStringProperty(dbName); + + dbTypeNameProp = new SimpleStringProperty(jdbcTypeName(dbType)); + + javaNamePrev = javaName; + + javaNameProp = new SimpleStringProperty(javaNamePrev); + + javaTypeNamePrev = javaTypeName; + + javaTypeNameProp = new SimpleStringProperty(javaTypeNamePrev); + + useProp = new SimpleBooleanProperty(true); + + keyPrev = key; + + keyProp = new SimpleBooleanProperty(keyPrev); + + this.nullable = nullable; + + akProp = new SimpleBooleanProperty(false); + + conversions = conversions(dbType, nullable, javaNamePrev); + + keyProp.addListener(new ChangeListener<Boolean>() { + @Override public void changed(ObservableValue<? extends Boolean> val, Boolean oldVal, Boolean newVal) { + if (newVal) { + if (!use()) + useProp.set(true); + } + else + akProp.set(false); + } + }); + + akProp.addListener(new ChangeListener<Boolean>() { + @Override public void changed(ObservableValue<? extends Boolean> val, Boolean oldVal, Boolean newVal) { + if (newVal && owner != null) { + keyProperty().set(true); + + for (PojoField field : owner.fields()) + if (field != PojoField.this && field.affinityKey()) + field.akProp.set(false); + } + } + }); + } + + /** + * @param jdbcType String name for JDBC type. + */ + private static String jdbcTypeName(int jdbcType) { + switch (jdbcType) { + case BIT: + return "BIT"; + case TINYINT: + return "TINYINT"; + case SMALLINT: + return "SMALLINT"; + case INTEGER: + return "INTEGER"; + case BIGINT: + return "BIGINT"; + case FLOAT: + return "FLOAT"; + case REAL: + return "REAL"; + case DOUBLE: + return "DOUBLE"; + case NUMERIC: + return "NUMERIC"; + case DECIMAL: + return "DECIMAL"; + case CHAR: + return "CHAR"; + case VARCHAR: + return "VARCHAR"; + case LONGVARCHAR: + return "LONGVARCHAR"; + case DATE: + return "DATE"; + case TIME: + return "TIME"; + case TIMESTAMP: + return "TIMESTAMP"; + case BINARY: + return "BINARY"; + case VARBINARY: + return "VARBINARY"; + case LONGVARBINARY: + return "LONGVARBINARY"; + case NULL: + return "NULL"; + case OTHER: + return "OTHER"; + case JAVA_OBJECT: + return "JAVA_OBJECT"; + case DISTINCT: + return "DISTINCT"; + case STRUCT: + return "STRUCT"; + case ARRAY: + return "ARRAY"; + case BLOB: + return "BLOB"; + case CLOB: + return "CLOB"; + case REF: + return "REF"; + case DATALINK: + return "DATALINK"; + case BOOLEAN: + return "BOOLEAN"; + case ROWID: + return "ROWID"; + case NCHAR: + return "NCHAR"; + case NVARCHAR: + return "NVARCHAR"; + case LONGNVARCHAR: + return "LONGNVARCHAR"; + case NCLOB: + return "NCLOB"; + case SQLXML: + return "SQLXML"; + default: + return "Unknown"; + } + } + + /** + * Revert changes to java names made by user. + */ + public void resetJavaName() { + javaNameProp.set(javaNamePrev); + } + + /** + * @param owner New field owner. + */ + public void owner(PojoDescriptor owner) { + this.owner = owner; + } + + /** + * @return {@code true} if filed should be used for code generation. + */ + public boolean use() { + return useProp.get(); + } + + /** + * @return {@code true} if this field belongs to primary key. + */ + public boolean key() { + return keyProp.get(); + } + + /** + * @param pk {@code true} if this field belongs to primary key. + */ + public void key(boolean pk) { + keyProp.set(pk); + } + + /** + * @return {@code true} if this field is an affinity key. + */ + public boolean affinityKey() { + return akProp.get(); + } + + /** + * @return POJO field java name. + */ + public String javaName() { + return javaNameProp.get(); + } + + /** + * @param name New POJO field java name. + */ + public void javaName(String name) { + javaNameProp.set(name); + } + + /** + * @return POJO field java type name. + */ + public String javaTypeName() { + return javaTypeNameProp.get(); + } + + /** + * @return Field name in database. + */ + public String dbName() { + return dbNameProp.get(); + } + + /** + * @return POJO field JDBC type name in database. + */ + public String dbTypeName() { + return dbTypeNameProp.get(); + } + + /** + * @return Is NULL allowed for field in database. + */ + public boolean nullable() { + return nullable; + } + + /** + * @return List of possible java type conversions. + */ + public ObservableList<String> conversions() { + return conversions; + } + + /** + * @return {@code true} if type of field is primitive type. + */ + public boolean primitive() { + return PRIMITIVES.contains(javaTypeName()); + } + + /** + * @return {@code true} if field was changed by user. + */ + public boolean changed() { + return keyPrev != key() || !javaNamePrev.equals(javaName()) || !javaTypeNamePrev.equals(javaTypeName()); + } + + /** + * @return Boolean property support for {@code use} property. + */ + public BooleanProperty useProperty() { + return useProp; + } + + /** + * @return Boolean property support for {@code key} property. + */ + public BooleanProperty keyProperty() { + return keyProp; + } + + /** + * @return Boolean property support for {@code affinityKey} property. + */ + public BooleanProperty affinityKeyProperty() { + return akProp; + } + + /** + * @return String property support for {@code javaName} property. + */ + public StringProperty javaNameProperty() { + return javaNameProp; + } + + /** + * @return String property support for {@code javaTypeName} property. + */ + public StringProperty javaTypeNameProperty() { + return javaTypeNameProp; + } + + /** + * @return String property support for {@code dbName} property. + */ + public StringProperty dbNameProperty() { + return dbNameProp; + } + + /** + * @return String property support for {@code dbName} property. + */ + public StringProperty dbTypeNameProperty() { + return dbTypeNameProp; + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/9cf45b43/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DatabaseMetadataParser.java ---------------------------------------------------------------------- diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DatabaseMetadataParser.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DatabaseMetadataParser.java new file mode 100644 index 0000000..696ca62 --- /dev/null +++ b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DatabaseMetadataParser.java @@ -0,0 +1,108 @@ +/* + * 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.apache.ignite.schema.parser; + +import javafx.collections.*; +import org.apache.ignite.schema.model.*; +import org.apache.ignite.schema.parser.dialect.*; + +import java.sql.*; +import java.util.*; +import java.util.logging.*; + +/** + * Database metadata parser. + */ +public class DatabaseMetadataParser { + /** Logger. */ + private static final Logger log = Logger.getLogger(DatabaseMetadataParser.class.getName()); + + /** + * Parse database metadata. + * + * @param conn Connection to database. + * @param tblsOnly If {@code true} then process tables only else process tables and views. + * @return Collection of POJO descriptors. + * @throws SQLException If parsing failed. + */ + public static ObservableList<PojoDescriptor> parse(Connection conn, boolean tblsOnly) throws SQLException { + DatabaseMetadataDialect dialect; + + try { + String dbProductName = conn.getMetaData().getDatabaseProductName(); + + if ("Oracle".equals(dbProductName)) + dialect = new OracleMetadataDialect(); + else if (dbProductName.startsWith("DB2/")) + dialect = new DB2MetadataDialect(); + else + dialect = new JdbcMetadataDialect(); + } + catch (SQLException e) { + log.log(Level.SEVERE, "Failed to resolve dialect (JdbcMetaDataDialect will be used.", e); + + dialect = new JdbcMetadataDialect(); + } + + Map<String, PojoDescriptor> parents = new HashMap<>(); + + Map<String, Collection<PojoDescriptor>> childrens = new HashMap<>(); + + for (DbTable tbl : dialect.tables(conn, tblsOnly)) { + String schema = tbl.schema(); + + PojoDescriptor parent = parents.get(schema); + Collection<PojoDescriptor> children = childrens.get(schema); + + if (parent == null) { + parent = new PojoDescriptor(null, new DbTable(schema, "", Collections.<DbColumn>emptyList(), + Collections.<String>emptySet(), Collections.<String>emptySet(), + Collections.<String, Map<String, Boolean>>emptyMap())); + + children = new ArrayList<>(); + + parents.put(schema, parent); + childrens.put(schema, children); + } + + children.add(new PojoDescriptor(parent, tbl)); + } + + List<PojoDescriptor> res = new ArrayList<>(); + + for (String schema : parents.keySet()) { + PojoDescriptor parent = parents.get(schema); + Collection<PojoDescriptor> children = childrens.get(schema); + + if (!children.isEmpty()) { + parent.children(children); + + res.add(parent); + res.addAll(children); + } + } + + Collections.sort(res, new Comparator<PojoDescriptor>() { + @Override public int compare(PojoDescriptor o1, PojoDescriptor o2) { + return o1.fullDbName().compareTo(o2.fullDbName()); + } + }); + + return FXCollections.observableList(res); + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/9cf45b43/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbColumn.java ---------------------------------------------------------------------- diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbColumn.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbColumn.java new file mode 100644 index 0000000..8b0c813 --- /dev/null +++ b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbColumn.java @@ -0,0 +1,76 @@ +/* + * 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.apache.ignite.schema.parser; + +/** + * Database table column. + */ +public class DbColumn { + /** Column name. */ + private final String name; + + /** Column JDBC type. */ + private final int type; + + /** Is this column belongs to primary key. */ + private final boolean key; + + /** Is {@code NULL} allowed for column in database. */ + private final boolean nullable; + + /** + * @param name Column name. + * @param type Column JDBC type. + * @param key {@code true} if this column belongs to primary key. + * @param nullable {@code true} if {@code NULL } allowed for column in database. + */ + public DbColumn(String name, int type, boolean key, boolean nullable) { + this.name = name; + this.type = type; + this.key = key; + this.nullable = nullable; + } + + /** + * @return Column name. + */ + public String name() { + return name; + } + + /** + * @return Column JDBC type. + */ + public int type() { + return type; + } + + /** + * @return {@code true} if this column belongs to primary key. + */ + public boolean key() { + return key; + } + + /** + * @return nullable {@code true} if {@code NULL } allowed for column in database. + */ + public boolean nullable() { + return nullable; + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/9cf45b43/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbTable.java ---------------------------------------------------------------------- diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbTable.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbTable.java new file mode 100644 index 0000000..35c7d91 --- /dev/null +++ b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbTable.java @@ -0,0 +1,105 @@ +/* + * 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.apache.ignite.schema.parser; + +import java.util.*; + +/** + * Database table. + */ +public class DbTable { + /** Schema name. */ + private final String schema; + + /** Table name. */ + private final String tbl; + + /** Columns. */ + private final Collection<DbColumn> cols; + + /** Columns in ascending order. */ + private final Set<String> ascCols; + + /** Columns in descending order. */ + private final Set<String> descCols; + + /** Indexes. */ + private final Map<String, Map<String, Boolean>> idxs; + + /** + * Default columns. + * + * @param schema Schema name. + * @param tbl Table name. + * @param cols Columns. + * @param ascCols Columns in ascending order. + * @param descCols Columns in descending order. + * @param idxs Indexes; + */ + public DbTable(String schema, String tbl, Collection<DbColumn> cols, Set<String> ascCols, Set<String> descCols, + Map<String, Map<String, Boolean>> idxs) { + this.schema = schema; + this.tbl = tbl; + this.cols = cols; + this.ascCols = ascCols; + this.descCols = descCols; + this.idxs = idxs; + } + + /** + * @return Schema name. + */ + public String schema() { + return schema; + } + + /** + * @return Table name. + */ + public String table() { + return tbl; + } + + /** + * @return Columns. + */ + public Collection<DbColumn> columns() { + return cols; + } + + /** + * @return Fields in ascending order + */ + public Set<String> ascendingColumns() { + return ascCols; + } + + /** + * @return Fields in descending order + */ + public Set<String> descendingColumns() { + return descCols; + } + + /** + * @return Indexes. + */ + public Map<String, Map<String, Boolean>> indexes() { + return idxs; + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/9cf45b43/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DB2MetadataDialect.java ---------------------------------------------------------------------- diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DB2MetadataDialect.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DB2MetadataDialect.java new file mode 100644 index 0000000..17eb8b2 --- /dev/null +++ b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DB2MetadataDialect.java @@ -0,0 +1,30 @@ +/* + * 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.apache.ignite.schema.parser.dialect; + +import java.util.*; + +/** + * DB2 specific metadata dialect. + */ +public class DB2MetadataDialect extends JdbcMetadataDialect { + /** {@inheritDoc} */ + @Override public Set<String> systemSchemas() { + return new HashSet<>(Arrays.asList("SYSIBM", "SYSCAT", "SYSSTAT", "SYSTOOLS")); + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/9cf45b43/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DatabaseMetadataDialect.java ---------------------------------------------------------------------- diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DatabaseMetadataDialect.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DatabaseMetadataDialect.java new file mode 100644 index 0000000..0d17567 --- /dev/null +++ b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DatabaseMetadataDialect.java @@ -0,0 +1,78 @@ +/* + * 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.apache.ignite.schema.parser.dialect; + +import org.apache.ignite.schema.parser.*; + +import java.sql.*; +import java.util.*; + +/** + * Base class for database metadata dialect. + */ +public abstract class DatabaseMetadataDialect { + /** + * Gets tables from database. + * + * @param conn Database connection. + * @param tblsOnly If {@code true} then gets only tables otherwise gets tables and views. + * @return Collection of table descriptors. + * @throws SQLException If failed to get tables. + */ + public abstract Collection<DbTable> tables(Connection conn, boolean tblsOnly) throws SQLException; + + /** + * @return Collection of database system schemas. + */ + public Set<String> systemSchemas() { + return Collections.singleton("INFORMATION_SCHEMA"); + } + + /** + * Create table descriptor. + * + * @param schema Schema name. + * @param tbl Table name. + * @param cols Table columns. + * @param idxs Table indexes. + * @return New {@code DbTable} instance. + */ + protected DbTable table(String schema, String tbl, Collection<DbColumn> cols, Map<String, Map<String, Boolean>>idxs) { + Set<String> ascCols = new HashSet<>(); + + Set<String> descCols = new HashSet<>(); + + for (Map<String, Boolean> idx : idxs.values()) { + if (idx.size() == 1) + for (Map.Entry<String, Boolean> idxCol : idx.entrySet()) { + String colName = idxCol.getKey(); + + Boolean desc = idxCol.getValue(); + + if (desc != null) { + if (desc) + descCols.add(colName); + else + ascCols.add(colName); + } + } + } + + return new DbTable(schema, tbl, cols, ascCols, descCols, idxs); + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/9cf45b43/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/JdbcMetadataDialect.java ---------------------------------------------------------------------- diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/JdbcMetadataDialect.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/JdbcMetadataDialect.java new file mode 100644 index 0000000..ab65e7a --- /dev/null +++ b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/JdbcMetadataDialect.java @@ -0,0 +1,141 @@ +/* + * 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.apache.ignite.schema.parser.dialect; + +import org.apache.ignite.schema.parser.*; + +import java.sql.*; +import java.util.*; + +/** + * Metadata dialect that uses standard JDBC for reading metadata. + */ +public class JdbcMetadataDialect extends DatabaseMetadataDialect { + /** */ + private static final String[] TABLES_ONLY = {"TABLE"}; + + /** */ + private static final String[] TABLES_AND_VIEWS = {"TABLE", "VIEW"}; + + /** Schema catalog index. */ + private static final int TBL_CATALOG_IDX = 1; + + /** Schema name index. */ + private static final int TBL_SCHEMA_IDX = 2; + + /** Table name index. */ + private static final int TBL_NAME_IDX = 3; + + /** Primary key column name index. */ + private static final int PK_COL_NAME_IDX = 4; + + /** Column name index. */ + private static final int COL_NAME_IDX = 4; + + /** Column data type index. */ + private static final int COL_DATA_TYPE_IDX = 5; + + /** Column nullable index. */ + private static final int COL_NULLABLE_IDX = 11; + + /** Index name index. */ + private static final int IDX_NAME_IDX = 6; + + /** Index column name index. */ + private static final int IDX_COL_NAME_IDX = 9; + + /** Index column descend index. */ + private static final int IDX_ASC_OR_DESC_IDX = 10; + + /** {@inheritDoc} */ + @Override public Collection<DbTable> tables(Connection conn, boolean tblsOnly) throws SQLException { + DatabaseMetaData dbMeta = conn.getMetaData(); + + Set<String> sys = systemSchemas(); + + Collection<DbTable> tbls = new ArrayList<>(); + + try (ResultSet tblsRs = dbMeta.getTables(null, null, "%", + tblsOnly ? TABLES_ONLY : TABLES_AND_VIEWS)) { + while (tblsRs.next()) { + String tblCatalog = tblsRs.getString(TBL_CATALOG_IDX); + String tblSchema = tblsRs.getString(TBL_SCHEMA_IDX); + String tblName = tblsRs.getString(TBL_NAME_IDX); + + // In case of MySql we should use catalog. + String schema = tblSchema != null ? tblSchema : tblCatalog; + + // Skip system schemas. + if (sys.contains(schema)) + continue; + + Set<String> pkCols = new HashSet<>(); + + try (ResultSet pkRs = dbMeta.getPrimaryKeys(tblCatalog, tblSchema, tblName)) { + while (pkRs.next()) + pkCols.add(pkRs.getString(PK_COL_NAME_IDX)); + } + + List<DbColumn> cols = new ArrayList<>(); + + try (ResultSet colsRs = dbMeta.getColumns(tblCatalog, tblSchema, tblName, null)) { + while (colsRs.next()) { + String colName = colsRs.getString(COL_NAME_IDX); + + cols.add(new DbColumn( + colName, + colsRs.getInt(COL_DATA_TYPE_IDX), + pkCols.contains(colName), + colsRs.getInt(COL_NULLABLE_IDX) == DatabaseMetaData.columnNullable)); + } + } + + Map<String, Map<String, Boolean>> idxs = new LinkedHashMap<>(); + + try (ResultSet idxRs = dbMeta.getIndexInfo(tblCatalog, tblSchema, tblName, false, true)) { + while (idxRs.next()) { + String idxName = idxRs.getString(IDX_NAME_IDX); + + String colName = idxRs.getString(IDX_COL_NAME_IDX); + + if (idxName == null || colName == null) + continue; + + Map<String, Boolean> idx = idxs.get(idxName); + + if (idx == null) { + idx = new LinkedHashMap<>(); + + idxs.put(idxName, idx); + } + + String askOrDesc = idxRs.getString(IDX_ASC_OR_DESC_IDX); + + Boolean desc = askOrDesc != null ? "D".equals(askOrDesc) : null; + + idx.put(colName, desc); + } + } + + tbls.add(table(schema, tblName, cols, idxs)); + } + } + + return tbls; + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/9cf45b43/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/OracleMetadataDialect.java ---------------------------------------------------------------------- diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/OracleMetadataDialect.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/OracleMetadataDialect.java new file mode 100644 index 0000000..6b16042 --- /dev/null +++ b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/OracleMetadataDialect.java @@ -0,0 +1,281 @@ +/* + * 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.apache.ignite.schema.parser.dialect; + +import org.apache.ignite.schema.parser.*; + +import java.sql.*; +import java.util.*; + +import static java.sql.Types.*; + +/** + * Oracle specific metadata dialect. + */ +public class OracleMetadataDialect extends DatabaseMetadataDialect { + /** SQL to get columns metadata. */ + private static final String SQL_COLUMNS = "SELECT a.owner, a.table_name, a.column_name, a.nullable," + + " a.data_type, a.data_precision, a.data_scale " + + "FROM all_tab_columns a %s" + + " WHERE a.owner = '%s'" + + " ORDER BY a.owner, a.table_name, a.column_id"; + + /** SQL to get list of PRIMARY KEYS columns. */ + private static final String SQL_PRIMARY_KEYS = "SELECT b.column_name" + + " FROM all_constraints a" + + " INNER JOIN all_cons_columns b ON a.owner = b.owner AND a.constraint_name = b.constraint_name" + + " WHERE a.owner = ? and a.table_name = ? AND a.constraint_type = 'P'"; + + /** SQL to get indexes metadata. */ + private static final String SQL_INDEXES = "select i.index_name, u.column_expression, i.column_name, i.descend" + + " FROM all_ind_columns i" + + " LEFT JOIN user_ind_expressions u on u.index_name = i.index_name and i.table_name = u.table_name" + + " WHERE i.index_owner = ? and i.table_name = ?" + + " ORDER BY i.index_name, i.column_position"; + + /** Owner index. */ + private static final int OWNER_IDX = 1; + + /** Table name index. */ + private static final int TBL_NAME_IDX = 2; + + /** Column name index. */ + private static final int COL_NAME_IDX = 3; + + /** Nullable index. */ + private static final int NULLABLE_IDX = 4; + + /** Data type index. */ + private static final int DATA_TYPE_IDX = 5; + + /** Numeric precision index. */ + private static final int DATA_PRECISION_IDX = 6; + + /** Numeric scale index. */ + private static final int DATA_SCALE_IDX = 7; + + /** Index name index. */ + private static final int IDX_NAME_IDX = 1; + + /** Index name index. */ + private static final int IDX_EXPR_IDX = 2; + + /** Index column name index. */ + private static final int IDX_COL_NAME_IDX = 3; + + /** Index column sort order index. */ + private static final int IDX_COL_DESCEND_IDX = 4; + + /** + * @param rs Result set with column type metadata from Oracle database. + * @return JDBC type. + * @throws SQLException If failed to decode type. + */ + private int decodeType(ResultSet rs) throws SQLException { + switch (rs.getString(DATA_TYPE_IDX)) { + case "CHAR": + case "NCHAR": + return CHAR; + + case "VARCHAR2": + case "NVARCHAR2": + return VARCHAR; + + case "LONG": + return LONGVARCHAR; + + case "LONG RAW": + return LONGVARBINARY; + + case "FLOAT": + return FLOAT; + + case "NUMBER": + int precision = rs.getInt(DATA_PRECISION_IDX); + int scale = rs.getInt(DATA_SCALE_IDX); + + if (scale > 0) { + if (scale < 4 && precision < 19) + return FLOAT; + + if (scale > 4 || precision > 19) + return DOUBLE; + + return NUMERIC; + } + else { + if (precision < 1) + return INTEGER; + + if (precision < 2) + return BOOLEAN; + + if (precision < 4) + return TINYINT; + + if (precision < 6) + return SMALLINT; + + if (precision < 11) + return INTEGER; + + if (precision < 20) + return BIGINT; + + return NUMERIC; + } + + case "DATE": + return DATE; + + case "TIMESTAMP": + return TIMESTAMP; + + case "BFILE": + case "BLOB": + return BLOB; + + case "CLOB": + case "NCLOB": + case "XMLTYPE": + return CLOB; + } + + return OTHER; + } + + /** + * Retrieve primary key columns. + * + * @param stmt Prepared SQL statement to execute. + * @param owner DB owner. + * @param tbl Table name. + * @return Primary key columns. + * @throws SQLException If failed to retrieve primary key columns. + */ + private Set<String> primaryKeys(PreparedStatement stmt, String owner, String tbl) throws SQLException { + Set<String> pkCols = new HashSet<>(); + + stmt.setString(1, owner); + stmt.setString(2, tbl); + + try (ResultSet pkRs = stmt.executeQuery()) { + while(pkRs.next()) + pkCols.add(pkRs.getString(1)); + } + + return pkCols; + } + + /** + * Retrieve index columns. + * + * @param stmt Prepared SQL statement to execute. + * @param owner DB owner. + * @param tbl Table name. + * @return Index columns. + * @throws SQLException If failed to retrieve indexe columns. + */ + private Map<String, Map<String, Boolean>> indexes(PreparedStatement stmt, String owner, String tbl) + throws SQLException { + Map<String, Map<String, Boolean>> idxs = new LinkedHashMap<>(); + + stmt.setString(1, owner); + stmt.setString(2, tbl); + + try (ResultSet idxsRs = stmt.executeQuery()) { + while (idxsRs.next()) { + String idxName = idxsRs.getString(IDX_NAME_IDX); + + Map<String, Boolean> idx = idxs.get(idxName); + + if (idx == null) { + idx = new LinkedHashMap<>(); + + idxs.put(idxName, idx); + } + + String expr = idxsRs.getString(IDX_EXPR_IDX); + + String col = expr == null ? idxsRs.getString(IDX_COL_NAME_IDX) : expr.replaceAll("\"", ""); + + idx.put(col, "DESC".equals(idxsRs.getString(IDX_COL_DESCEND_IDX))); + } + } + + return idxs; + } + + /** {@inheritDoc} */ + @Override public Collection<DbTable> tables(Connection conn, boolean tblsOnly) throws SQLException { + Collection<DbTable> tbls = new ArrayList<>(); + + PreparedStatement pkStmt = conn.prepareStatement(SQL_PRIMARY_KEYS); + + PreparedStatement idxStmt = conn.prepareStatement(SQL_INDEXES); + + try (Statement colsStmt = conn.createStatement()) { + Collection<DbColumn> cols = new ArrayList<>(); + + Set<String> pkCols = Collections.emptySet(); + Map<String, Map<String, Boolean>> idxs = Collections.emptyMap(); + + String user = conn.getMetaData().getUserName().toUpperCase(); + + String sql = String.format(SQL_COLUMNS, + tblsOnly ? "INNER JOIN all_tables b on a.table_name = b.table_name" : "", user); + + try (ResultSet colsRs = colsStmt.executeQuery(sql)) { + String prevSchema = ""; + String prevTbl = ""; + + boolean first = true; + + while (colsRs.next()) { + String owner = colsRs.getString(OWNER_IDX); + String tbl = colsRs.getString(TBL_NAME_IDX); + + boolean changed = !owner.equals(prevSchema) || !tbl.equals(prevTbl); + + if (changed) { + if (first) + first = false; + else + tbls.add(table(prevSchema, prevTbl, cols, idxs)); + + prevSchema = owner; + prevTbl = tbl; + cols = new ArrayList<>(); + pkCols = primaryKeys(pkStmt, owner, tbl); + idxs = indexes(idxStmt, owner, tbl); + } + + String colName = colsRs.getString(COL_NAME_IDX); + + cols.add(new DbColumn(colName, decodeType(colsRs), pkCols.contains(colName), + !"N".equals(colsRs.getString(NULLABLE_IDX)))); + } + + if (!cols.isEmpty()) + tbls.add(table(prevSchema, prevTbl, cols, idxs)); + } + } + + return tbls; + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/9cf45b43/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/ConfirmCallable.java ---------------------------------------------------------------------- diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/ConfirmCallable.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/ConfirmCallable.java new file mode 100644 index 0000000..3990496 --- /dev/null +++ b/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/ConfirmCallable.java @@ -0,0 +1,81 @@ +/* + * 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.apache.ignite.schema.ui; + +import javafx.application.*; +import javafx.stage.*; + +import java.util.concurrent.*; + +import static org.apache.ignite.schema.ui.MessageBox.Result.*; + +/** + * Callable to ask user for confirmation from non EDT thread. + */ +public class ConfirmCallable implements Callable<MessageBox.Result> { + /** Owner window. */ + private final Stage owner; + + /** Message template. */ + private final String template; + + /** Message to show in confirmation dialog. */ + private String msg; + + /** User choice. */ + private MessageBox.Result choice = NO; + + /** + * @param owner Owner window. + * @param template Message template. + */ + public ConfirmCallable(Stage owner, String template) { + this.owner = owner; + this.template = template; + } + + /** {@inheritDoc} */ + @Override public MessageBox.Result call() throws Exception { + choice = MessageBox.confirmRememberChoiceDialog(owner, String.format(template, msg)); + + return choice; + } + + /** + * Execute confirmation in EDT thread. + * + * @return Confirm result. + */ + public MessageBox.Result confirm(String msg) { + this.msg = msg; + + if (choice == YES_TO_ALL || choice == NO_TO_ALL) + return choice; + + FutureTask<MessageBox.Result> fut = new FutureTask<>(this); + + Platform.runLater(fut); + + try { + return fut.get(); + } + catch (Exception ignored) { + return NO; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/9cf45b43/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/Controls.java ---------------------------------------------------------------------- diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/Controls.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/Controls.java new file mode 100644 index 0000000..e270edb --- /dev/null +++ b/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/Controls.java @@ -0,0 +1,661 @@ +/* + * 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.apache.ignite.schema.ui; + +import com.sun.javafx.scene.control.skin.*; +import javafx.application.*; +import javafx.beans.value.*; +import javafx.collections.*; +import javafx.event.*; +import javafx.geometry.*; +import javafx.scene.*; +import javafx.scene.control.*; +import javafx.scene.control.cell.*; +import javafx.scene.image.*; +import javafx.scene.input.*; +import javafx.scene.layout.*; +import javafx.scene.text.*; +import javafx.util.*; + +/** + * Utility class to create controls. + */ +public class Controls { + /** */ + public static final Insets DFLT_PADDING = new Insets(10, 10, 10, 10); + + /** + * Create scene with predefined style. + * + * @param root The root node of the scene graph. + * @return New {@code Scene} instance. + */ + public static Scene scene(Parent root) { + Scene scene = new Scene(root); + + scene.getStylesheets().add("media/style.css"); + + return scene; + } + + /** + * Create grid pane with default padding. + * + * @param top Top padding + * @param right Right padding. + * @param bottom Bottom padding. + * @param left Left padding. + * @return New {@code GridPaneEx} instance. + */ + public static GridPaneEx paneEx(double top, double right, double bottom, double left) { + GridPaneEx paneEx = new GridPaneEx(); + + paneEx.setPadding(new Insets(top, right, bottom, left)); + + return paneEx; + } + + /** + * Create new {@code HBox} with default padding. + * + * @param spacing Amount of horizontal space between each child. + * @param dfltPadding If {@code true} than set default padding for pane. + * @return New {@code HBox} instance. + */ + public static HBox hBox(int spacing, boolean dfltPadding) { + HBox hb = new HBox(spacing); + + if (dfltPadding) + hb.setPadding(DFLT_PADDING); + + return hb; + } + + /** + * Create new {@code HBox} with default padding and add controls. + * + * @param spacing Amount of horizontal space between each child. + * @param dfltPadding If {@code true} than set default padding for pane. + * @param controls Controls to add. + * @return New {@code HBox} instance. + */ + public static HBox hBox(int spacing, boolean dfltPadding, Node... controls) { + HBox hb = hBox(spacing, dfltPadding); + + hb.getChildren().addAll(controls); + + return hb; + } + + /** + * Create new {@code VBox} with default padding. + * + * @param spacing Amount of horizontal space between each child. + * @return New {@code VBox} instance. + */ + public static VBox vBox(int spacing) { + VBox vb = new VBox(spacing); + + vb.setPadding(DFLT_PADDING); + + return vb; + } + + /** + * Create new {@code VBox} with default padding and add controls. + * + * @param spacing Amount of horizontal space between each child. + * @param controls Controls to add. + * @return New {@code VBox} instance. + */ + public static VBox vBox(int spacing, Node... controls) { + VBox vb = vBox(spacing); + + vb.getChildren().addAll(controls); + + return vb; + } + + /** + * Create stack pane. + * + * @param controls Controls to add. + * @return New {@code StackPane} instance. + */ + public static StackPane stackPane(Node... controls) { + StackPane sp = new StackPane(); + + sp.getChildren().addAll(controls); + + return sp; + } + + /** + * Create border pane. + * + * @param top Optional top control. + * @param center Optional center control. + * @param bottom Optional bottom control. + * @param left Optional left control. + * @param right Optional right control. + * @return New {@code BorderPane} instance. + */ + public static BorderPane borderPane(Node top, Node center, Node bottom, Node left, Node right) { + BorderPane bp = new BorderPane(); + + bp.setTop(top); + bp.setCenter(center); + bp.setBottom(bottom); + bp.setLeft(left); + bp.setRight(right); + + return bp; + } + + /** + * Sets control tooltip if needed. + * + * @param ctrl Target control. + * @param tip Tooltip text. + * @return Control itself for method chaining. + */ + public static <T extends Control> T tooltip(T ctrl, String tip) { + if (!tip.isEmpty()) + ctrl.setTooltip(new Tooltip(tip)); + + return ctrl; + } + + /** + * Create label. + * + * @param text Label text. + * @return New {@code Label} instance. + */ + public static Label label(String text) { + return new Label(text); + } + + /** + * Create button with text only. + * + * @param text Button text. + * @param tip Tooltip text. + * @param onAct Button action. + * @return New {@code Button} instance. + */ + public static Button button(String text, String tip, EventHandler<ActionEvent> onAct) { + Button btn = new Button(text); + + btn.setOnAction(onAct); + + tooltip(btn, tip); + + return btn; + } + + /** + * Create button with icon only. + * + * @param icon Button icon. + * @param tip Tooltip text. + * @param onAct Button action. + * @return New {@code Button} instance. + */ + public static Button button(ImageView icon, String tip, EventHandler<ActionEvent> onAct) { + Button btn = new Button(); + + btn.setGraphic(icon); + btn.setOnAction(onAct); + + tooltip(btn, tip); + + return btn; + } + + /** + * Create pane with buttons. + * + * @param alignment Alignment of buttons. + * @param dfltPadding If {@code true} than set default padding for pane. + * @param btns Buttons that will be added to pane. + * @return New {@code HBox} instance with buttons. + */ + public static Pane buttonsPane(Pos alignment, boolean dfltPadding, Button... btns) { + HBox hb = hBox(10, dfltPadding, btns); + + hb.setAlignment(alignment); + + return hb; + } + + /** + * Create checkbox. + * + * @param text Checkbox text. + * @param tip Tooltip tex. + * @param sel Checkbox selected state. + * @return New {@code Checkbox} instance. + */ + public static CheckBox checkBox(String text, String tip, boolean sel) { + CheckBox ch = new CheckBox(text); + + ch.setSelected(sel); + + tooltip(ch, tip); + + return ch; + } + + /** + * Create text field. + * + * @param tip Tooltip text. + * @return New {@code TextField} instance. + */ + public static TextField textField(String tip) { + TextField tf = new TextField(); + + tooltip(tf, tip); + + return tf; + } + + /** + * Create static text. + * + * @param text Text to show. + * @param wrap Text wrapping width. + * @return New {@code Text} instance. + */ + public static Text text(String text, int wrap) { + Text t = new Text(text); + + t.setFont(new Font(14)); + + if (wrap > 0) + t.setWrappingWidth(wrap); + + return t; + } + + /** + * Create password field. + * + * @param tip Tooltip text. + * @return New {@code PasswordField} instance. + */ + public static PasswordField passwordField(String tip) { + PasswordField pf = new PasswordField(); + + tooltip(pf, tip); + + return pf; + } + + /** + * Create combo box. + * + * @param tip Tooltip text. + * @param items Combo box items. + * @return New {@code ComboBox} instance. + */ + public static <T> ComboBox<T> comboBox(String tip, T... items) { + ComboBox<T> cb = new ComboBox<>(FXCollections.observableArrayList(items)); + + cb.setMaxWidth(Double.MAX_VALUE); + cb.getSelectionModel().select(0); + + tooltip(cb, tip); + + return cb; + } + + /** + * Create split pane for provided nodes. + * + * @param node1 First node. + * @param node2 Second node. + * @param pos Initial divider position. + * @return New {@code SplitPane} instance. + */ + public static SplitPane splitPane(Node node1, Node node2, double pos) { + SplitPane sp = new SplitPane(); + + sp.setOrientation(Orientation.VERTICAL); + sp.getItems().addAll(node1, node2); + sp.setDividerPosition(0, pos); + + return sp; + } + + /** + * Create titled pane. + * + * @param title Title. + * @param node Node. + * @return New {@code TitledPane} instance. + */ + public static TitledPane titledPane(String title, Node node) { + TitledPane tp = new TitledPane(title, node); + + tp.setExpanded(false); + + return tp; + } + + /** + * Create table column. + * + * @param colName Column name to display. + * @param propName Property name column is bound to. + * @param tip Column tooltip text. + * @param minWidth The minimum width column is permitted to be resized to. + * @param maxWidth The maximum width column is permitted to be resized to. + * @param editable {@code true} if column is editable. + * @return New {@code TableColumn} instance. + */ + private static <S, T> TableColumn<S, T> tableColumn(String colName, String propName, String tip, + int minWidth, int maxWidth, boolean editable) { + TableColumn<S, T> col = new TableColumn<>(); + + col.setGraphic(tooltip(new Label(colName), tip)); + + col.setSortable(false); + + if (minWidth > 0) + col.setMinWidth(minWidth); + + if (maxWidth > 0) + col.setMaxWidth(maxWidth); + + col.setCellValueFactory(new PropertyValueFactory<S, T>(propName)); + + col.setEditable(editable); + + return col; + } + + /** + * Create table column. + * + * @param colName Column name to display. + * @param propName Property name column is bound to. + * @param tip Column tooltip text. + * @return New {@code TableColumn} instance. + */ + public static <S, T> TableColumn<S, T> tableColumn(String colName, String propName, String tip) { + return tableColumn(colName, propName, tip, 100, 0, false); + } + + /** + * Create table column. + * + * @param colName Column name to display. + * @param propName Property name column is bound to. + * @param tip Column tooltip text. + * @param cellFactory Custom cell factory. + * @return New {@code TableColumn} instance. + */ + public static <S, T> TableColumn<S, T> customColumn(String colName, String propName, String tip, + Callback<TableColumn<S, T>, TableCell<S, T>> cellFactory) { + TableColumn<S, T> col = tableColumn(colName, propName, tip, 100, 0, true); + + col.setCellFactory(cellFactory); + + return col; + } + + /** + * Create editable boolean table column. + * + * @param colName Column name to display. + * @param propName Property name column is bound to. + * @param tip Column tooltip text. + * @return New {@code TableColumn} instance. + */ + public static <S> TableColumn<S, Boolean> booleanColumn(String colName, String propName, String tip) { + TableColumn<S, Boolean> col = tableColumn(colName, propName, tip, 50, 50, true); + + col.setCellFactory(CheckBoxTableCellEx.<S>cellFactory()); + + return col; + + } + + /** + * Create editable text table column. + * + * @param colName Column name to display. + * @param propName Property name column is bound to. + * @param tip Column tooltip text. + * @return New {@code TableColumn} instance. + */ + public static <S> TableColumn<S, String> textColumn(String colName, String propName, String tip, + TextColumnValidator<S> validator) { + TableColumn<S, String> col = tableColumn(colName, propName, tip, 100, 0, true); + + col.setCellFactory(TextFieldTableCellEx.cellFactory(validator)); + + return col; + } + + /** + * Create table view. + * + * @param placeholder Text to show if table model is empty. + * @param cols Columns to add. + * @return New {@code TableView} instance. + */ + public static <S> TableView<S> tableView(String placeholder, TableColumn<S, ?>... cols) { + TableView<S> tbl = new TableView<>(); + + tbl.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + tbl.setEditable(true); + tbl.setMinHeight(70); + tbl.setPlaceholder(text(placeholder, 0)); + + tbl.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + + tbl.getColumns().addAll(cols); + + return tbl; + } + + /** + * Create progress indicator. + * + * @param sz Indicator diameter. + * @return New {@code ProgressIndicator} instance. + */ + public static ProgressIndicator progressIndicator(int sz) { + ProgressIndicator pi = new ProgressIndicator(); + + pi.setMaxWidth(sz); + pi.setMaxHeight(sz); + + return pi; + } + + /** + * Create image view. + * + * @param imgFileName Image filename. + * @return New {@code ImageView} instance. + */ + public static ImageView imageView(String imgFileName, int sz) { + return new ImageView(image(imgFileName, sz)); + } + + /** + * Gets image by its filename. + * + * @param imgFileName Image filename. + * @return Loaded image. + */ + public static Image image(String imgFileName, int sz) { + return new Image(Controls.class.getClassLoader() + .getResourceAsStream(String.format("media/%1$s_%2$dx%2$d.png", imgFileName, sz))); + } + + /** + * Customized checkbox. + */ + private static class CheckBoxTableCellEx<S> extends CheckBoxTableCell<S, Boolean> { + /** Creates a ComboBox cell factory for use in TableColumn controls. */ + public static <S> Callback<TableColumn<S, Boolean>, TableCell<S, Boolean>> cellFactory() { + return new Callback<TableColumn<S, Boolean>, TableCell<S, Boolean>>() { + @Override public TableCell<S, Boolean> call(TableColumn<S, Boolean> col) { + return new CheckBoxTableCellEx<>(); + } + }; + } + + /** + * Default constructor. + */ + private CheckBoxTableCellEx() { + setAlignment(Pos.CENTER); + } + } + + /** + * Special table text field cell that commit its content on focus lost. + */ + private static class TextFieldTableCellEx<S> extends TextFieldTableCell<S, String> { + /** */ + private final TextColumnValidator<S> validator; + /** */ + private boolean cancelling; + /** */ + private boolean hardCancel; + /** */ + private String curTxt = ""; + + /** Row value. */ + private S rowVal; + + /** Create cell factory. */ + public static <S> Callback<TableColumn<S, String>, TableCell<S, String>> + cellFactory(final TextColumnValidator<S> validator) { + return new Callback<TableColumn<S, String>, TableCell<S, String>>() { + @Override public TableCell<S, String> call(TableColumn<S, String> col) { + return new TextFieldTableCellEx<>(validator); + } + }; + } + + /** + * Text field cell constructor. + * + * @param validator Input text validator. + */ + private TextFieldTableCellEx(TextColumnValidator<S> validator) { + this.validator = validator; + } + + /** {@inheritDoc} */ + @Override public void startEdit() { + String item = getItem(); + + if (item == null || item.isEmpty()) + return; + + super.startEdit(); + + rowVal = getTableView().getSelectionModel().getSelectedItem(); + + curTxt = ""; + + hardCancel = false; + + Node g = getGraphic(); + + if (g != null) { + final TextField tf = (TextField)g; + + tf.textProperty().addListener(new ChangeListener<String>() { + @Override public void changed(ObservableValue<? extends String> val, String oldVal, String newVal) { + curTxt = newVal; + } + }); + + tf.setOnKeyPressed(new EventHandler<KeyEvent>() { + @Override public void handle(KeyEvent evt) { + if (KeyCode.ENTER == evt.getCode()) + cancelEdit(); + else if (KeyCode.ESCAPE == evt.getCode()) { + hardCancel = true; + + cancelEdit(); + } + } + }); + + tf.setOnKeyReleased(new EventHandler<KeyEvent>() { + @Override public void handle(KeyEvent evt) { + // No-op to overwrite JavaFX implementation. + } + }); + + // Special hack for editable TextFieldTableCell. + // Cancel edit when focus lost from text field, but do not cancel if focus lost to VirtualFlow. + tf.focusedProperty().addListener(new ChangeListener<Boolean>() { + @Override public void changed(ObservableValue<? extends Boolean> val, Boolean oldVal, Boolean newVal) { + Node fo = getScene().getFocusOwner(); + + if (!newVal) { + if (fo instanceof VirtualFlow) { + if (fo.getParent().getParent() != getTableView()) + cancelEdit(); + } + else + cancelEdit(); + } + } + }); + + Platform.runLater(new Runnable() { + @Override public void run() { + tf.requestFocus(); + } + }); + } + } + + /** {@inheritDoc} */ + @Override public void cancelEdit() { + if (cancelling) + super.cancelEdit(); + else + try { + cancelling = true; + + if (hardCancel || curTxt.trim().isEmpty()) + super.cancelEdit(); + else if (validator.valid(rowVal, curTxt)) + commitEdit(curTxt); + else + super.cancelEdit(); + } + finally { + cancelling = false; + } + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/9cf45b43/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/GridPaneEx.java ---------------------------------------------------------------------- diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/GridPaneEx.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/GridPaneEx.java new file mode 100644 index 0000000..be1aae9 --- /dev/null +++ b/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/GridPaneEx.java @@ -0,0 +1,177 @@ +/* + * 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.apache.ignite.schema.ui; + +import javafx.geometry.*; +import javafx.scene.*; +import javafx.scene.control.*; +import javafx.scene.layout.*; + +/** + * Utility extension of {@code GridPane}. + */ +public class GridPaneEx extends GridPane { + /** Current column. */ + private int col; + + /** Current row. */ + private int row; + + /** + * Create pane. + */ + public GridPaneEx() { + setAlignment(Pos.TOP_LEFT); + setHgap(5); + setVgap(10); + } + + /** + * Add default column. + */ + public void addColumn() { + getColumnConstraints().add(new ColumnConstraints()); + } + + /** + * Add column with constraints and horizontal grow priority for the column. + * + * @param min Column minimum size. + * @param pref Column preferred size. + * @param max Column max size. + * @param hgrow Column horizontal grow priority. + */ + public void addColumn(double min, double pref, double max, Priority hgrow) { + ColumnConstraints cc = new ColumnConstraints(min, pref, max); + + cc.setHgrow(hgrow); + + getColumnConstraints().add(cc); + } + + /** + * Add default row. + */ + public void addRow() { + getRowConstraints().add(new RowConstraints()); + } + + /** + * Add default rows. + * + * @param n Number of rows to add. + */ + public void addRows(int n) { + for (int i = 0; i < n; i++) + addRow(); + } + + /** + * Add row with constraints and vertical grow priority for the row. + * + * @param min Row minimum size. + * @param pref Row preferred size. + * @param max Row max size. + * @param vgrow Row vertical grow priority. + */ + public void addRow(double min, double pref, double max, Priority vgrow) { + RowConstraints rc = new RowConstraints(min, pref, max); + + rc.setVgrow(vgrow); + + getRowConstraints().add(rc); + } + + /** + * Wrap to next row. + */ + public void wrap() { + col = 0; + + row++; + } + + /** + * Skip columns. + * + * @param span How many columns should be skipped. + */ + public void skip(int span) { + add(new Label(""), span); + } + + /** + * Move to next column. + */ + private void nextCol(int span) { + col += span; + + if (col >= getColumnConstraints().size()) + wrap(); + } + + /** + * Add control to grid pane. + * + * @param ctrl Control to add. + * @param span How many columns control should take. + * @return Added control. + */ + public <T extends Node> T add(T ctrl, int span) { + add(ctrl, col, row, span, 1); + + nextCol(span); + + return ctrl; + } + + /** + * Add control to grid pane. + * + * @param ctrl Control to add. + * @return Added control. + */ + public <T extends Node> T add(T ctrl) { + return add(ctrl, 1); + } + + /** + * Add control with label. + * + * @param text Label text. + * @param ctrl Control to add. + * @param span How many columns control should take. + * @return Added control. + */ + public <T extends Node> T addLabeled(String text, T ctrl, int span) { + add(new Label(text)); + + return add(ctrl, span); + } + + /** + * Add control with label. + * + * @param text Label text. + * @param ctrl Control to add. + * @return Added control. + */ + public <T extends Node> T addLabeled(String text, T ctrl) { + return addLabeled(text, ctrl, 1); + } +}