Index: extensions/modeshape-connector-jdbc-metadata/src/main/java/org/modeshape/connector/meta/jdbc/JdbcMetadataRepository.java =================================================================== --- extensions/modeshape-connector-jdbc-metadata/src/main/java/org/modeshape/connector/meta/jdbc/JdbcMetadataRepository.java (revision 1824) +++ extensions/modeshape-connector-jdbc-metadata/src/main/java/org/modeshape/connector/meta/jdbc/JdbcMetadataRepository.java (working copy) @@ -26,43 +26,29 @@ package org.modeshape.connector.meta.jdbc; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; import java.util.Map; -import java.util.Set; import java.util.UUID; +import net.jcip.annotations.NotThreadSafe; import net.jcip.annotations.ThreadSafe; import org.modeshape.common.util.Logger; -import org.modeshape.graph.ModeShapeLexicon; import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.JcrLexicon; -import org.modeshape.graph.JcrNtLexicon; -import org.modeshape.graph.connector.LockFailedException; +import org.modeshape.graph.ModeShapeLexicon; import org.modeshape.graph.connector.RepositorySourceException; -import org.modeshape.graph.connector.path.DefaultPathNode; -import org.modeshape.graph.connector.path.PathNode; -import org.modeshape.graph.connector.path.PathRepository; -import org.modeshape.graph.connector.path.PathWorkspace; -import org.modeshape.graph.connector.path.cache.WorkspaceCache; +import org.modeshape.graph.connector.base.PathNode; +import org.modeshape.graph.connector.base.PathTransaction; +import org.modeshape.graph.connector.base.Repository; import org.modeshape.graph.property.Name; -import org.modeshape.graph.property.NameFactory; import org.modeshape.graph.property.Path; -import org.modeshape.graph.property.PathFactory; -import org.modeshape.graph.property.PathNotFoundException; import org.modeshape.graph.property.Property; import org.modeshape.graph.property.PropertyFactory; import org.modeshape.graph.property.Path.Segment; -import org.modeshape.graph.query.QueryResults; -import org.modeshape.graph.request.AccessQueryRequest; -import org.modeshape.graph.request.LockBranchRequest.LockScope; +import org.modeshape.graph.request.InvalidWorkspaceException; @ThreadSafe -public class JdbcMetadataRepository extends PathRepository { +public class JdbcMetadataRepository extends Repository { public final static String TABLES_SEGMENT_NAME = "tables"; public final static String PROCEDURES_SEGMENT_NAME = "procedures"; @@ -81,13 +67,12 @@ public class JdbcMetadataRepository extends PathRepository { initialize(); } + final JdbcMetadataSource source() { + return source; + } + @Override protected synchronized void initialize() { - if (!this.workspaces.isEmpty()) return; - - String defaultWorkspaceName = getDefaultWorkspaceName(); - this.workspaces.put(defaultWorkspaceName, new JdbcMetadataWorkspace(defaultWorkspaceName)); - ExecutionContext context = source.getRepositoryContext().getExecutionContext(); PropertyFactory propFactory = context.getPropertyFactory(); this.rootNodeProperties = new HashMap(); @@ -121,10 +106,8 @@ public class JdbcMetadataRepository extends PathRepository { } finally { closeConnection(conn); } - } - public WorkspaceCache getCache( String workspaceName ) { - return source.getPathRepositoryCache().getCache(workspaceName); + super.initialize(); } Connection getConnection() { @@ -144,661 +127,44 @@ public class JdbcMetadataRepository extends PathRepository { } } - @ThreadSafe - @SuppressWarnings( "synthetic-access" ) - private class JdbcMetadataWorkspace implements PathWorkspace { - - private final String name; - private final WorkspaceCache cache; - - JdbcMetadataWorkspace( String name ) { - this.name = name; - cache = getCache(name); - } - - public Path getLowestExistingPath( Path path ) { - PathFactory pathFactory = source.getRepositoryContext().getExecutionContext().getValueFactories().getPathFactory(); - - Path lastWorkingPath = pathFactory.createRootPath(); - - for (Segment segment : path.getSegmentsList()) { - Path newPathToTry = pathFactory.create(lastWorkingPath, segment); - - try { - getNode(newPathToTry); - lastWorkingPath = newPathToTry; - } catch (PathNotFoundException pnfe) { - return lastWorkingPath; - } - } - - // If we got here, someone called getLowestExistingPath on a path that was invalid but now isn't. - return path; - } - - public String getName() { - return this.name; - } - - public PathNode getNode( Path path ) { - assert path != null; - - PathNode node = cache.get(path); - if (node != null) return node; - - List segments = path.getSegmentsList(); - switch (segments.size()) { - case 0: - node = getRoot(); - break; - case 1: - node = catalogNodeFor(segments); - break; - case 2: - node = schemaNodeFor(segments); - break; - case 3: - if (TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName())) { - node = tablesNodeFor(segments); - } else if (PROCEDURES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName())) { - node = proceduresNodeFor(segments); - } - break; - case 4: - if (TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName())) { - node = tableNodeFor(segments); - } else if (PROCEDURES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName())) { - node = procedureNodeFor(segments); - } - break; - case 5: - if (TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName())) { - node = columnNodeFor(segments); - } - break; - default: - return null; - } - - if (node != null) cache.set(node); - return node; - } - - private PathNode catalogNodeFor( List segments ) throws RepositorySourceException { - assert segments != null; - assert segments.size() == 1; - - List schemaNames = new LinkedList(); - ExecutionContext context = source.getRepositoryContext().getExecutionContext(); - PathFactory pathFactory = context.getValueFactories().getPathFactory(); - PropertyFactory propFactory = context.getPropertyFactory(); - - Path nodePath = pathFactory.createAbsolutePath(segments); - - Connection conn = getConnection(); - String catalogName = segments.get(0).getName().getLocalName(); - - try { - MetadataCollector meta = source.getMetadataCollector(); - if (catalogName.equals(source.getDefaultCatalogName())) catalogName = null; - - // Make sure that this is a valid catalog for this database - List catalogNames = meta.getCatalogNames(conn); - - /* - * If a "real" (not default) catalog name is provided but it is not a valid - * catalog name for this database OR if the default catalog name is being used - * but this database uses real catalog names, then no catalog with that name exists. - * - * This gets complicated by the fact that some DBMSes use an empty string for a catalog - * which also gets mapped to the default catalog name in our system - */ - boolean catalogMatchesDefaultName = catalogNames.isEmpty() || catalogNames.contains(""); - - if ((catalogName != null && !catalogNames.contains(catalogName)) - || (catalogName == null && !catalogMatchesDefaultName)) { - return null; - } - - List schemaNamesFromMeta = new ArrayList(meta.getSchemaNames(conn, catalogName)); - - for (String schemaName : schemaNamesFromMeta) { - if (schemaName.length() > 0) { - schemaNames.add(pathFactory.createSegment(schemaName)); - } - } - - if (schemaNames.isEmpty()) { - schemaNames.add(pathFactory.createSegment(source.getDefaultSchemaName())); - } - - Map properties = new HashMap(); - properties.put(JcrLexicon.PRIMARY_TYPE, propFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.UNSTRUCTURED)); - properties.put(JcrLexicon.MIXIN_TYPES, propFactory.create(JcrLexicon.MIXIN_TYPES, JdbcMetadataLexicon.CATALOG)); - - return new DefaultPathNode(nodePath, null, properties, schemaNames); - } catch (JdbcMetadataException se) { - throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetSchemaNames.text(catalogName), se); - } finally { - closeConnection(conn); - } - } - - private PathNode schemaNodeFor( List segments ) throws RepositorySourceException { - assert segments != null; - assert segments.size() == 2; - - ExecutionContext context = source.getRepositoryContext().getExecutionContext(); - PathFactory pathFactory = context.getValueFactories().getPathFactory(); - PropertyFactory propFactory = context.getPropertyFactory(); - - Path nodePath = pathFactory.createAbsolutePath(segments); - - Connection conn = getConnection(); - String catalogName = segments.get(0).getName().getLocalName(); - if (catalogName.equals(source.getDefaultCatalogName())) catalogName = null; - - String schemaName = segments.get(1).getName().getLocalName(); - if (schemaName.equals(source.getDefaultSchemaName())) schemaName = null; - - try { - MetadataCollector meta = source.getMetadataCollector(); - - // Make sure that the schema exists in the given catalog - List schemaNames = meta.getSchemaNames(conn, catalogName); - - /* - * If a "real" (not default) catalog name is provided but it is not a valid - * catalog name for this database OR if the default catalog name is being used - * but this database uses real catalog names, then no catalog with that name exists. - */ - if ((schemaName != null && !schemaNames.contains(schemaName)) || (schemaName == null && !schemaNames.isEmpty())) { - return null; - } - - Map properties = new HashMap(); - properties.put(JcrLexicon.PRIMARY_TYPE, propFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.UNSTRUCTURED)); - properties.put(JcrLexicon.MIXIN_TYPES, propFactory.create(JcrLexicon.MIXIN_TYPES, JdbcMetadataLexicon.SCHEMA)); - - Segment[] children = new Segment[] {pathFactory.createSegment(TABLES_SEGMENT_NAME), - pathFactory.createSegment(PROCEDURES_SEGMENT_NAME)}; - - return new DefaultPathNode(nodePath, null, properties, Arrays.asList(children)); - } catch (JdbcMetadataException se) { - throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetSchemaNames.text(catalogName), se); - } finally { - closeConnection(conn); - } - } - - private PathNode tablesNodeFor( List segments ) throws RepositorySourceException { - assert segments != null; - assert segments.size() == 3; - assert TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName()); - - ExecutionContext context = source.getRepositoryContext().getExecutionContext(); - PathFactory pathFactory = context.getValueFactories().getPathFactory(); - PropertyFactory propFactory = context.getPropertyFactory(); - - Path nodePath = pathFactory.createAbsolutePath(segments); - - Connection conn = getConnection(); - String catalogName = segments.get(0).getName().getLocalName(); - if (catalogName.equals(source.getDefaultCatalogName())) catalogName = null; - - String schemaName = segments.get(1).getName().getLocalName(); - if (schemaName.equals(source.getDefaultSchemaName())) schemaName = null; - - try { - MetadataCollector meta = source.getMetadataCollector(); - - // Make sure that the schema exists in the given catalog - List schemaNames = meta.getSchemaNames(conn, catalogName); - - /* - * If a "real" (not default) catalog name is provided but it is not a valid - * catalog name for this database OR if the default catalog name is being used - * but this database uses real catalog names, then no catalog with that name exists. - */ - if ((schemaName != null && !schemaNames.contains(schemaName)) || (schemaName == null && !schemaNames.isEmpty())) { - return null; - } - - Map properties = new HashMap(); - properties.put(JcrLexicon.PRIMARY_TYPE, propFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.UNSTRUCTURED)); - properties.put(JcrLexicon.MIXIN_TYPES, propFactory.create(JcrLexicon.MIXIN_TYPES, JdbcMetadataLexicon.TABLES)); - - List tables = meta.getTables(conn, catalogName, schemaName, null); - List children = new ArrayList(tables.size()); - - for (TableMetadata table : tables) { - children.add(pathFactory.createSegment(table.getName())); - } - - return new DefaultPathNode(nodePath, null, properties, children); - } catch (JdbcMetadataException se) { - throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetTableNames.text(catalogName, schemaName), se); - } finally { - closeConnection(conn); - } - } - - private PathNode tableNodeFor( List segments ) throws RepositorySourceException { - assert segments != null; - assert segments.size() == 4; - assert TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName()); - - ExecutionContext context = source.getRepositoryContext().getExecutionContext(); - PathFactory pathFactory = context.getValueFactories().getPathFactory(); - PropertyFactory propFactory = context.getPropertyFactory(); - - Path nodePath = pathFactory.createAbsolutePath(segments); - - Connection conn = getConnection(); - String catalogName = segments.get(0).getName().getLocalName(); - if (catalogName.equals(source.getDefaultCatalogName())) catalogName = null; - - String schemaName = segments.get(1).getName().getLocalName(); - if (schemaName.equals(source.getDefaultSchemaName())) schemaName = null; - - String tableName = segments.get(3).getName().getLocalName(); - - try { - MetadataCollector meta = source.getMetadataCollector(); - - List tables = meta.getTables(conn, catalogName, schemaName, tableName); - - // Make sure that the table exists in the given catalog and schema - if (tables.isEmpty()) { - return null; - } - assert tables.size() == 1; - TableMetadata table = tables.get(0); - - Map properties = new HashMap(); - Name propName; - propName = JcrLexicon.PRIMARY_TYPE; - properties.put(propName, propFactory.create(propName, JcrNtLexicon.UNSTRUCTURED)); - propName = JcrLexicon.MIXIN_TYPES; - properties.put(propName, propFactory.create(propName, JdbcMetadataLexicon.TABLE)); - - if (table.getType() != null) { - propName = JdbcMetadataLexicon.TABLE_TYPE; - properties.put(propName, propFactory.create(propName, table.getType())); - } - if (table.getDescription() != null) { - propName = JdbcMetadataLexicon.DESCRIPTION; - properties.put(propName, propFactory.create(propName, table.getDescription())); - } - if (table.getTypeCatalogName() != null) { - propName = JdbcMetadataLexicon.TYPE_CATALOG_NAME; - properties.put(propName, propFactory.create(propName, table.getTypeCatalogName())); - } - if (table.getTypeSchemaName() != null) { - propName = JdbcMetadataLexicon.TYPE_SCHEMA_NAME; - properties.put(propName, propFactory.create(propName, table.getTypeSchemaName())); - } - if (table.getTypeName() != null) { - propName = JdbcMetadataLexicon.TYPE_NAME; - properties.put(propName, propFactory.create(propName, table.getTypeName())); - } - if (table.getSelfReferencingColumnName() != null) { - propName = JdbcMetadataLexicon.SELF_REFERENCING_COLUMN_NAME; - properties.put(propName, propFactory.create(propName, table.getSelfReferencingColumnName())); - } - if (table.getReferenceGenerationStrategyName() != null) { - propName = JdbcMetadataLexicon.REFERENCE_GENERATION_STRATEGY_NAME; - properties.put(propName, propFactory.create(propName, table.getReferenceGenerationStrategyName())); - } - - List columns = meta.getColumns(conn, catalogName, schemaName, tableName, null); - List children = new ArrayList(columns.size()); - - for (ColumnMetadata column : columns) { - children.add(pathFactory.createSegment(column.getName())); - } - - return new DefaultPathNode(nodePath, null, properties, children); - } catch (JdbcMetadataException se) { - throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetTable.text(catalogName, schemaName, tableName), - se); - } finally { - closeConnection(conn); - } - } - - private PathNode proceduresNodeFor( List segments ) throws RepositorySourceException { - assert segments != null; - assert segments.size() == 3; - assert PROCEDURES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName()); - - ExecutionContext context = source.getRepositoryContext().getExecutionContext(); - PathFactory pathFactory = context.getValueFactories().getPathFactory(); - PropertyFactory propFactory = context.getPropertyFactory(); - - Path nodePath = pathFactory.createAbsolutePath(segments); - - Connection conn = getConnection(); - String catalogName = segments.get(0).getName().getLocalName(); - if (catalogName.equals(source.getDefaultCatalogName())) catalogName = null; - - String schemaName = segments.get(1).getName().getLocalName(); - if (schemaName.equals(source.getDefaultSchemaName())) schemaName = null; - - try { - MetadataCollector meta = source.getMetadataCollector(); - - // Make sure that the schema exists in the given catalog - List schemaNames = meta.getSchemaNames(conn, catalogName); - - /* - * If a "real" (not default) catalog name is provided but it is not a valid - * catalog name for this database OR if the default catalog name is being used - * but this database uses real catalog names, then no catalog with that name exists. - */ - if ((schemaName != null && !schemaNames.contains(schemaName)) || (schemaName == null && !schemaNames.isEmpty())) { - return null; - } - - Map properties = new HashMap(); - properties.put(JcrLexicon.PRIMARY_TYPE, propFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.UNSTRUCTURED)); - properties.put(JcrLexicon.MIXIN_TYPES, propFactory.create(JcrLexicon.MIXIN_TYPES, JdbcMetadataLexicon.PROCEDURES)); - - List procedures = meta.getProcedures(conn, catalogName, schemaName, null); - List children = new ArrayList(procedures.size()); - - for (ProcedureMetadata procedure : procedures) { - children.add(pathFactory.createSegment(procedure.getName())); - } - - return new DefaultPathNode(nodePath, null, properties, children); - } catch (JdbcMetadataException se) { - throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetProcedureNames.text(catalogName, schemaName), se); - } finally { - closeConnection(conn); - } - } - - private PathNode procedureNodeFor( List segments ) throws RepositorySourceException { - assert segments != null; - assert segments.size() == 4; - assert PROCEDURES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName()); - - ExecutionContext context = source.getRepositoryContext().getExecutionContext(); - PathFactory pathFactory = context.getValueFactories().getPathFactory(); - PropertyFactory propFactory = context.getPropertyFactory(); - - Path nodePath = pathFactory.createAbsolutePath(segments); - - Connection conn = getConnection(); - String catalogName = segments.get(0).getName().getLocalName(); - if (catalogName.equals(source.getDefaultCatalogName())) catalogName = null; - - String schemaName = segments.get(1).getName().getLocalName(); - if (schemaName.equals(source.getDefaultSchemaName())) schemaName = null; - - String procedureName = segments.get(3).getName().getLocalName(); - - try { - MetadataCollector meta = source.getMetadataCollector(); - - List procedures = meta.getProcedures(conn, catalogName, schemaName, procedureName); - - // Make sure that the table exists in the given catalog and schema - if (procedures.isEmpty()) { - return null; - } - - /* - * Some RDMSes support overloaded procedures and thus can return multiple records for the - * same procedure name in the same catalog and schema (e.g., HSQLDB and the Math.abs procedure). - * - * That means that: - * 1. CollectorMetadata.getProcedures(...) needs to consider overloaded procedures when determining - * the stable order in which the procedures should be returned - * 2. Procedure nodes can have an SNS index > 1 - */ - if (segments.get(3).getIndex() > procedures.size()) { - return null; - } - - ProcedureMetadata procedure = procedures.get(segments.get(3).getIndex() - 1); - - Map properties = new HashMap(); - Name propName; - propName = JcrLexicon.PRIMARY_TYPE; - properties.put(propName, propFactory.create(propName, JcrNtLexicon.UNSTRUCTURED)); - propName = JcrLexicon.MIXIN_TYPES; - properties.put(propName, propFactory.create(propName, JdbcMetadataLexicon.PROCEDURE)); - - if (procedure.getDescription() != null) { - propName = JdbcMetadataLexicon.DESCRIPTION; - properties.put(propName, propFactory.create(propName, procedure.getDescription())); - } - propName = JdbcMetadataLexicon.PROCEDURE_RETURN_TYPE; - properties.put(propName, propFactory.create(propName, procedure.getType())); - - return new DefaultPathNode(nodePath, null, properties, Collections.emptyList()); - } catch (JdbcMetadataException se) { - throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetProcedure.text(catalogName, - schemaName, - procedureName), se); - } finally { - closeConnection(conn); - } - } - - private PathNode columnNodeFor( List segments ) throws RepositorySourceException { - assert segments != null; - assert segments.size() == 5; - assert TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName()); - - ExecutionContext context = source.getRepositoryContext().getExecutionContext(); - PathFactory pathFactory = context.getValueFactories().getPathFactory(); - PropertyFactory propFactory = context.getPropertyFactory(); - - Path nodePath = pathFactory.createAbsolutePath(segments); - - Connection conn = getConnection(); - String catalogName = segments.get(0).getName().getLocalName(); - if (catalogName.equals(source.getDefaultCatalogName())) catalogName = null; - - String schemaName = segments.get(1).getName().getLocalName(); - if (schemaName.equals(source.getDefaultSchemaName())) schemaName = null; - - String tableName = segments.get(3).getName().getLocalName(); - String columnName = segments.get(4).getName().getLocalName(); - - try { - MetadataCollector meta = source.getMetadataCollector(); - - List columns = meta.getColumns(conn, catalogName, schemaName, tableName, columnName); - - // Make sure that the column exists in the given table, catalog, and schema - if (columns.isEmpty()) { - return null; - } - - assert columns.size() == 1 : "Duplicate column named " + columnName; - ColumnMetadata column = columns.get(0); - - Map properties = new HashMap(); - Name propName; - propName = JcrLexicon.PRIMARY_TYPE; - properties.put(propName, propFactory.create(propName, JcrNtLexicon.UNSTRUCTURED)); - propName = JcrLexicon.MIXIN_TYPES; - properties.put(propName, propFactory.create(propName, JdbcMetadataLexicon.COLUMN)); - - propName = JdbcMetadataLexicon.JDBC_DATA_TYPE; - properties.put(propName, propFactory.create(propName, column.getJdbcDataType())); - propName = JdbcMetadataLexicon.TYPE_NAME; - properties.put(propName, propFactory.create(propName, column.getTypeName())); - propName = JdbcMetadataLexicon.COLUMN_SIZE; - properties.put(propName, propFactory.create(propName, column.getColumnSize())); - propName = JdbcMetadataLexicon.DECIMAL_DIGITS; - properties.put(propName, propFactory.create(propName, column.getDecimalDigits())); - propName = JdbcMetadataLexicon.RADIX; - properties.put(propName, propFactory.create(propName, column.getRadix())); - if (column.getNullable() != null) { - propName = JdbcMetadataLexicon.NULLABLE; - properties.put(propName, propFactory.create(propName, column.getNullable())); - } - if (column.getDescription() != null) { - propName = JdbcMetadataLexicon.DESCRIPTION; - properties.put(propName, propFactory.create(propName, column.getDescription())); - } - if (column.getDefaultValue() != null) { - propName = JdbcMetadataLexicon.DEFAULT_VALUE; - properties.put(propName, propFactory.create(propName, column.getDefaultValue())); - } - propName = JdbcMetadataLexicon.LENGTH; - properties.put(propName, propFactory.create(propName, column.getLength())); - propName = JdbcMetadataLexicon.ORDINAL_POSITION; - properties.put(propName, propFactory.create(propName, column.getOrdinalPosition())); - if (column.getScopeCatalogName() != null) { - propName = JdbcMetadataLexicon.SCOPE_CATALOG_NAME; - - properties.put(propName, propFactory.create(propName, column.getScopeCatalogName())); - } - if (column.getScopeSchemaName() != null) { - propName = JdbcMetadataLexicon.SCOPE_SCHEMA_NAME; - properties.put(propName, propFactory.create(propName, column.getScopeSchemaName())); - } - if (column.getScopeTableName() != null) { - propName = JdbcMetadataLexicon.SCOPE_TABLE_NAME; - properties.put(propName, propFactory.create(propName, column.getScopeTableName())); - } - if (column.getSourceJdbcDataType() != null) { - propName = JdbcMetadataLexicon.SOURCE_JDBC_DATA_TYPE; - properties.put(propName, propFactory.create(propName, column.getSourceJdbcDataType())); - } - return new DefaultPathNode(nodePath, null, properties, Collections.emptyList()); - } catch (JdbcMetadataException se) { - throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetColumn.text(catalogName, - schemaName, - tableName, - columnName), se); - } finally { - closeConnection(conn); - } - } - - public PathNode getRoot() throws RepositorySourceException { - List catalogNames = new LinkedList(); - ExecutionContext context = source.getRepositoryContext().getExecutionContext(); - PathFactory pathFactory = context.getValueFactories().getPathFactory(); - - Connection conn = getConnection(); - try { - MetadataCollector meta = source.getMetadataCollector(); - - for (String catalogName : meta.getCatalogNames(conn)) { - if (catalogName.length() > 0) { - catalogNames.add(pathFactory.createSegment(catalogName)); - } - } - - if (catalogNames.isEmpty()) { - // This database must not support catalogs - catalogNames.add(pathFactory.createSegment(source.getDefaultCatalogName())); - } - - return new RootNode(catalogNames); - } catch (JdbcMetadataException se) { - throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetCatalogNames.text(), se); - } finally { - closeConnection(conn); - } - } - - /** - * This connector does not support connector-level, persistent locking of nodes. - * - * @param node - * @param lockScope - * @param lockTimeoutInMillis - * @throws LockFailedException - */ - public void lockNode( PathNode node, - LockScope lockScope, - long lockTimeoutInMillis ) throws LockFailedException { - // Locking is not supported by this connector - } - - /** - * This connector does not support connector-level, persistent locking of nodes. - * - * @param node the node to be unlocked - */ - public void unlockNode( PathNode node ) { - // Locking is not supported by this connector - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.connector.map.MapWorkspace#query(org.modeshape.graph.ExecutionContext, - * org.modeshape.graph.request.AccessQueryRequest) - */ - public QueryResults query( ExecutionContext context, - AccessQueryRequest accessQuery ) { - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.connector.map.MapWorkspace#search(org.modeshape.graph.ExecutionContext, java.lang.String) - */ - public QueryResults search( ExecutionContext context, - String fullTextSearchExpression ) { - return null; - } + final Map rootNodeProperties() { + return rootNodeProperties; } - @SuppressWarnings( "synthetic-access" ) - private class RootNode implements PathNode { - private final List catalogNames; - - private RootNode( List catalogNames ) { - this.catalogNames = catalogNames; - } + @Override + public JdbcMetadataTransaction startTransaction( ExecutionContext context, + boolean readonly ) { + return new JdbcMetadataTransaction(this, source.getRootNodeUuidObject()); + } - public List getChildSegments() { - return catalogNames; - } + @NotThreadSafe + protected class JdbcMetadataTransaction extends PathTransaction { - public Path getPath() { - ExecutionContext context = source.getRepositoryContext().getExecutionContext(); - return context.getValueFactories().getPathFactory().createRootPath(); + public JdbcMetadataTransaction( Repository repository, + UUID rootNodeUuid ) { + super(repository, rootNodeUuid); } - public UUID getUuid() { - return source.getRootNodeUuid(); + @Override + protected PathNode createNode( Segment name, + Path parentPath, + Iterable properties ) { + throw new RepositorySourceException(JdbcMetadataI18n.sourceIsReadOnly.text(source().getName())); } - public Map getProperties() { - return rootNodeProperties; + public boolean destroyWorkspace( JdbcMetadataWorkspace workspace ) throws InvalidWorkspaceException { + throw new RepositorySourceException(JdbcMetadataI18n.sourceIsReadOnly.text(source().getName())); } - public Property getProperty( ExecutionContext context, - String name ) { - NameFactory nameFactory = context.getValueFactories().getNameFactory(); - return rootNodeProperties.get(nameFactory.create(name)); - } + public JdbcMetadataWorkspace getWorkspace( String name, + JdbcMetadataWorkspace originalToClone ) throws InvalidWorkspaceException { + if (name.equals(source().getDefaultWorkspaceName())) { + return new JdbcMetadataWorkspace(JdbcMetadataRepository.this, name); + } - public Property getProperty( Name name ) { - return rootNodeProperties.get(name); + throw new InvalidWorkspaceException(JdbcMetadataI18n.sourceIsReadOnly.text(source().getName())); } - public Set getUniqueChildNames() { - Set childNames = new HashSet(catalogNames.size()); - - for (Segment catalogName : catalogNames) { - childNames.add(catalogName.getName()); - } - return childNames; - } } } Index: extensions/modeshape-connector-jdbc-metadata/src/main/java/org/modeshape/connector/meta/jdbc/JdbcMetadataSource.java =================================================================== --- extensions/modeshape-connector-jdbc-metadata/src/main/java/org/modeshape/connector/meta/jdbc/JdbcMetadataSource.java (revision 1824) +++ extensions/modeshape-connector-jdbc-metadata/src/main/java/org/modeshape/connector/meta/jdbc/JdbcMetadataSource.java (working copy) @@ -35,16 +35,19 @@ import javax.sql.DataSource; import net.jcip.annotations.ThreadSafe; import org.modeshape.common.i18n.I18n; import org.modeshape.common.util.Logger; +import org.modeshape.connector.meta.jdbc.JdbcMetadataRepository.JdbcMetadataTransaction; import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.connector.RepositoryConnection; import org.modeshape.graph.connector.RepositorySourceCapabilities; import org.modeshape.graph.connector.RepositorySourceException; -import org.modeshape.graph.connector.path.AbstractPathRepositorySource; -import org.modeshape.graph.connector.path.PathRepositoryConnection; +import org.modeshape.graph.connector.base.AbstractRepositorySource; +import org.modeshape.graph.connector.base.Connection; +import org.modeshape.graph.connector.base.PathNode; +import org.modeshape.graph.request.CreateWorkspaceRequest.CreateConflictBehavior; import com.mchange.v2.c3p0.ComboPooledDataSource; @ThreadSafe -public class JdbcMetadataSource extends AbstractPathRepositorySource implements ObjectFactory { +public class JdbcMetadataSource extends AbstractRepositorySource implements ObjectFactory { private static final long serialVersionUID = 1L; @@ -144,6 +147,8 @@ public class JdbcMetadataSource extends AbstractPathRepositorySource implements private transient JdbcMetadataRepository repository; private transient MetadataCollector metadataCollector = DEFAULT_METADATA_COLLECTOR; + private ExecutionContext defaultContext = new ExecutionContext(); + final JdbcMetadataRepository repository() { return this.repository; } @@ -240,9 +245,17 @@ public class JdbcMetadataSource extends AbstractPathRepositorySource implements if (repository == null) { repository = new JdbcMetadataRepository(this); - } - return new PathRepositoryConnection(this, repository); + ExecutionContext context = repositoryContext != null ? repositoryContext.getExecutionContext() : defaultContext; + JdbcMetadataTransaction txn = repository.startTransaction(context, true); + try { + repository.createWorkspace(txn, getDefaultWorkspaceName(), CreateConflictBehavior.DO_NOT_CREATE, null); + } finally { + txn.commit(); + } + + } + return new Connection(this, repository); } public Reference getReference() { @@ -251,7 +264,7 @@ public class JdbcMetadataSource extends AbstractPathRepositorySource implements Reference ref = new Reference(className, factoryClassName, null); ref.add(new StringRefAddr(SOURCE_NAME, getName())); - ref.add(new StringRefAddr(ROOT_NODE_UUID, getRootNodeUuid().toString())); + ref.add(new StringRefAddr(ROOT_NODE_UUID, getRootNodeUuidObject().toString())); ref.add(new StringRefAddr(DATA_SOURCE_JNDI_NAME, getDataSourceJndiName())); ref.add(new StringRefAddr(USERNAME, getUsername())); ref.add(new StringRefAddr(PASSWORD, getPassword())); @@ -310,7 +323,7 @@ public class JdbcMetadataSource extends AbstractPathRepositorySource implements // Create the source instance ... JdbcMetadataSource source = new JdbcMetadataSource(); if (sourceName != null) source.setName(sourceName); - if (rootNodeUuid != null) source.setRootNodeUuid(rootNodeUuid); + if (rootNodeUuid != null) source.setRootNodeUuidObject(rootNodeUuid); if (dataSourceJndiName != null) source.setDataSourceJndiName(dataSourceJndiName); if (username != null) source.setUsername(username); if (password != null) source.setPassword(password); Index: extensions/modeshape-connector-jdbc-metadata/src/main/java/org/modeshape/connector/meta/jdbc/JdbcMetadataWorkspace.java new file mode 100644 =================================================================== --- /dev/null (revision 1824) +++ extensions/modeshape-connector-jdbc-metadata/src/main/java/org/modeshape/connector/meta/jdbc/JdbcMetadataWorkspace.java (working copy) @@ -0,0 +1,574 @@ +package org.modeshape.connector.meta.jdbc; + +import java.sql.Connection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import net.jcip.annotations.NotThreadSafe; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.JcrLexicon; +import org.modeshape.graph.JcrNtLexicon; +import org.modeshape.graph.connector.RepositorySourceException; +import org.modeshape.graph.connector.base.PathNode; +import org.modeshape.graph.connector.base.PathWorkspace; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Path; +import org.modeshape.graph.property.PathFactory; +import org.modeshape.graph.property.Property; +import org.modeshape.graph.property.PropertyFactory; +import org.modeshape.graph.property.Path.Segment; + +/** + * Workspace implementation for the JDBC metadata connector. + */ +@NotThreadSafe +class JdbcMetadataWorkspace extends PathWorkspace { + + public final static String TABLES_SEGMENT_NAME = "tables"; + public final static String PROCEDURES_SEGMENT_NAME = "procedures"; + + protected final JdbcMetadataRepository repository; + + public JdbcMetadataWorkspace( JdbcMetadataRepository repository, + String name ) { + super(name, repository.source().getRootNodeUuidObject()); + this.repository = repository; + } + + @Override + public PathNode getNode( Path path ) { + assert path != null; + + PathNode node = null; + + List segments = path.getSegmentsList(); + switch (segments.size()) { + case 0: + node = getRootNode(); + break; + case 1: + node = catalogNodeFor(segments); + break; + case 2: + node = schemaNodeFor(segments); + break; + case 3: + if (TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName())) { + node = tablesNodeFor(segments); + } else if (PROCEDURES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName())) { + node = proceduresNodeFor(segments); + } + break; + case 4: + if (TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName())) { + node = tableNodeFor(segments); + } else if (PROCEDURES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName())) { + node = procedureNodeFor(segments); + } + break; + case 5: + if (TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName())) { + node = columnNodeFor(segments); + } + break; + default: + return null; + } + + return node; + } + + private PathNode catalogNodeFor( List segments ) throws RepositorySourceException { + assert segments != null; + assert segments.size() == 1; + + List schemaNames = new LinkedList(); + ExecutionContext context = repository.source().getRepositoryContext().getExecutionContext(); + PathFactory pathFactory = context.getValueFactories().getPathFactory(); + PropertyFactory propFactory = context.getPropertyFactory(); + + Path nodePath = pathFactory.createAbsolutePath(segments); + + Connection conn = repository.getConnection(); + String catalogName = segments.get(0).getName().getLocalName(); + + try { + MetadataCollector meta = repository.source().getMetadataCollector(); + if (catalogName.equals(repository.source().getDefaultCatalogName())) catalogName = null; + + // Make sure that this is a valid catalog for this database + List catalogNames = meta.getCatalogNames(conn); + + /* + * If a "real" (not default) catalog name is provided but it is not a valid + * catalog name for this database OR if the default catalog name is being used + * but this database uses real catalog names, then no catalog with that name exists. + * + * This gets complicated by the fact that some DBMSes use an empty string for a catalog + * which also gets mapped to the default catalog name in our system + */ + boolean catalogMatchesDefaultName = catalogNames.isEmpty() || catalogNames.contains(""); + + if ((catalogName != null && !catalogNames.contains(catalogName)) + || (catalogName == null && !catalogMatchesDefaultName)) { + return null; + } + + List schemaNamesFromMeta = new ArrayList(meta.getSchemaNames(conn, catalogName)); + + for (String schemaName : schemaNamesFromMeta) { + if (schemaName.length() > 0) { + schemaNames.add(pathFactory.createSegment(schemaName)); + } + } + + if (schemaNames.isEmpty()) { + schemaNames.add(pathFactory.createSegment(repository.source().getDefaultSchemaName())); + } + + Map properties = new HashMap(); + properties.put(JcrLexicon.PRIMARY_TYPE, propFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.UNSTRUCTURED)); + properties.put(JcrLexicon.MIXIN_TYPES, propFactory.create(JcrLexicon.MIXIN_TYPES, JdbcMetadataLexicon.CATALOG)); + + return new PathNode(null, nodePath.getParent(), nodePath.getLastSegment(), properties, schemaNames); + } catch (JdbcMetadataException se) { + throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetSchemaNames.text(catalogName), se); + } finally { + repository.closeConnection(conn); + } + } + + private PathNode schemaNodeFor( List segments ) throws RepositorySourceException { + assert segments != null; + assert segments.size() == 2; + + ExecutionContext context = repository.source().getRepositoryContext().getExecutionContext(); + PathFactory pathFactory = context.getValueFactories().getPathFactory(); + PropertyFactory propFactory = context.getPropertyFactory(); + + Path nodePath = pathFactory.createAbsolutePath(segments); + + Connection conn = repository.getConnection(); + String catalogName = segments.get(0).getName().getLocalName(); + if (catalogName.equals(repository.source().getDefaultCatalogName())) catalogName = null; + + String schemaName = segments.get(1).getName().getLocalName(); + if (schemaName.equals(repository.source().getDefaultSchemaName())) schemaName = null; + + try { + MetadataCollector meta = repository.source().getMetadataCollector(); + + // Make sure that the schema exists in the given catalog + List schemaNames = meta.getSchemaNames(conn, catalogName); + + /* + * If a "real" (not default) catalog name is provided but it is not a valid + * catalog name for this database OR if the default catalog name is being used + * but this database uses real catalog names, then no catalog with that name exists. + */ + if ((schemaName != null && !schemaNames.contains(schemaName)) || (schemaName == null && !schemaNames.isEmpty())) { + return null; + } + + Map properties = new HashMap(); + properties.put(JcrLexicon.PRIMARY_TYPE, propFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.UNSTRUCTURED)); + properties.put(JcrLexicon.MIXIN_TYPES, propFactory.create(JcrLexicon.MIXIN_TYPES, JdbcMetadataLexicon.SCHEMA)); + + Segment[] children = new Segment[] {pathFactory.createSegment(TABLES_SEGMENT_NAME), + pathFactory.createSegment(PROCEDURES_SEGMENT_NAME)}; + return new PathNode(null, nodePath.getParent(), nodePath.getLastSegment(), properties, Arrays.asList(children)); + } catch (JdbcMetadataException se) { + throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetSchemaNames.text(catalogName), se); + } finally { + repository.closeConnection(conn); + } + } + + private PathNode tablesNodeFor( List segments ) throws RepositorySourceException { + assert segments != null; + assert segments.size() == 3; + assert TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName()); + + ExecutionContext context = repository.source().getRepositoryContext().getExecutionContext(); + PathFactory pathFactory = context.getValueFactories().getPathFactory(); + PropertyFactory propFactory = context.getPropertyFactory(); + + Path nodePath = pathFactory.createAbsolutePath(segments); + + Connection conn = repository.getConnection(); + String catalogName = segments.get(0).getName().getLocalName(); + if (catalogName.equals(repository.source().getDefaultCatalogName())) catalogName = null; + + String schemaName = segments.get(1).getName().getLocalName(); + if (schemaName.equals(repository.source().getDefaultSchemaName())) schemaName = null; + + try { + MetadataCollector meta = repository.source().getMetadataCollector(); + + // Make sure that the schema exists in the given catalog + List schemaNames = meta.getSchemaNames(conn, catalogName); + + /* + * If a "real" (not default) catalog name is provided but it is not a valid + * catalog name for this database OR if the default catalog name is being used + * but this database uses real catalog names, then no catalog with that name exists. + */ + if ((schemaName != null && !schemaNames.contains(schemaName)) || (schemaName == null && !schemaNames.isEmpty())) { + return null; + } + + Map properties = new HashMap(); + properties.put(JcrLexicon.PRIMARY_TYPE, propFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.UNSTRUCTURED)); + properties.put(JcrLexicon.MIXIN_TYPES, propFactory.create(JcrLexicon.MIXIN_TYPES, JdbcMetadataLexicon.TABLES)); + + List tables = meta.getTables(conn, catalogName, schemaName, null); + List children = new ArrayList(tables.size()); + + for (TableMetadata table : tables) { + children.add(pathFactory.createSegment(table.getName())); + } + + return new PathNode(null, nodePath.getParent(), nodePath.getLastSegment(), properties, children); + } catch (JdbcMetadataException se) { + throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetTableNames.text(catalogName, schemaName), se); + } finally { + repository.closeConnection(conn); + } + } + + private PathNode tableNodeFor( List segments ) throws RepositorySourceException { + assert segments != null; + assert segments.size() == 4; + assert TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName()); + + ExecutionContext context = repository.source().getRepositoryContext().getExecutionContext(); + PathFactory pathFactory = context.getValueFactories().getPathFactory(); + PropertyFactory propFactory = context.getPropertyFactory(); + + Path nodePath = pathFactory.createAbsolutePath(segments); + + Connection conn = repository.getConnection(); + String catalogName = segments.get(0).getName().getLocalName(); + if (catalogName.equals(repository.source().getDefaultCatalogName())) catalogName = null; + + String schemaName = segments.get(1).getName().getLocalName(); + if (schemaName.equals(repository.source().getDefaultSchemaName())) schemaName = null; + + String tableName = segments.get(3).getName().getLocalName(); + + try { + MetadataCollector meta = repository.source().getMetadataCollector(); + + List tables = meta.getTables(conn, catalogName, schemaName, tableName); + + // Make sure that the table exists in the given catalog and schema + if (tables.isEmpty()) { + return null; + } + assert tables.size() == 1; + TableMetadata table = tables.get(0); + + Map properties = new HashMap(); + Name propName; + propName = JcrLexicon.PRIMARY_TYPE; + properties.put(propName, propFactory.create(propName, JcrNtLexicon.UNSTRUCTURED)); + propName = JcrLexicon.MIXIN_TYPES; + properties.put(propName, propFactory.create(propName, JdbcMetadataLexicon.TABLE)); + + if (table.getType() != null) { + propName = JdbcMetadataLexicon.TABLE_TYPE; + properties.put(propName, propFactory.create(propName, table.getType())); + } + if (table.getDescription() != null) { + propName = JdbcMetadataLexicon.DESCRIPTION; + properties.put(propName, propFactory.create(propName, table.getDescription())); + } + if (table.getTypeCatalogName() != null) { + propName = JdbcMetadataLexicon.TYPE_CATALOG_NAME; + properties.put(propName, propFactory.create(propName, table.getTypeCatalogName())); + } + if (table.getTypeSchemaName() != null) { + propName = JdbcMetadataLexicon.TYPE_SCHEMA_NAME; + properties.put(propName, propFactory.create(propName, table.getTypeSchemaName())); + } + if (table.getTypeName() != null) { + propName = JdbcMetadataLexicon.TYPE_NAME; + properties.put(propName, propFactory.create(propName, table.getTypeName())); + } + if (table.getSelfReferencingColumnName() != null) { + propName = JdbcMetadataLexicon.SELF_REFERENCING_COLUMN_NAME; + properties.put(propName, propFactory.create(propName, table.getSelfReferencingColumnName())); + } + if (table.getReferenceGenerationStrategyName() != null) { + propName = JdbcMetadataLexicon.REFERENCE_GENERATION_STRATEGY_NAME; + properties.put(propName, propFactory.create(propName, table.getReferenceGenerationStrategyName())); + } + + List columns = meta.getColumns(conn, catalogName, schemaName, tableName, null); + List children = new ArrayList(columns.size()); + + for (ColumnMetadata column : columns) { + children.add(pathFactory.createSegment(column.getName())); + } + + return new PathNode(null, nodePath.getParent(), nodePath.getLastSegment(), properties, children); + + } catch (JdbcMetadataException se) { + throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetTable.text(catalogName, schemaName, tableName), se); + } finally { + repository.closeConnection(conn); + } + } + + private PathNode proceduresNodeFor( List segments ) throws RepositorySourceException { + assert segments != null; + assert segments.size() == 3; + assert PROCEDURES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName()); + + ExecutionContext context = repository.source().getRepositoryContext().getExecutionContext(); + PathFactory pathFactory = context.getValueFactories().getPathFactory(); + PropertyFactory propFactory = context.getPropertyFactory(); + + Path nodePath = pathFactory.createAbsolutePath(segments); + + Connection conn = repository.getConnection(); + String catalogName = segments.get(0).getName().getLocalName(); + if (catalogName.equals(repository.source().getDefaultCatalogName())) catalogName = null; + + String schemaName = segments.get(1).getName().getLocalName(); + if (schemaName.equals(repository.source().getDefaultSchemaName())) schemaName = null; + + try { + MetadataCollector meta = repository.source().getMetadataCollector(); + + // Make sure that the schema exists in the given catalog + List schemaNames = meta.getSchemaNames(conn, catalogName); + + /* + * If a "real" (not default) catalog name is provided but it is not a valid + * catalog name for this database OR if the default catalog name is being used + * but this database uses real catalog names, then no catalog with that name exists. + */ + if ((schemaName != null && !schemaNames.contains(schemaName)) || (schemaName == null && !schemaNames.isEmpty())) { + return null; + } + + Map properties = new HashMap(); + properties.put(JcrLexicon.PRIMARY_TYPE, propFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.UNSTRUCTURED)); + properties.put(JcrLexicon.MIXIN_TYPES, propFactory.create(JcrLexicon.MIXIN_TYPES, JdbcMetadataLexicon.PROCEDURES)); + + List procedures = meta.getProcedures(conn, catalogName, schemaName, null); + List children = new ArrayList(procedures.size()); + + for (ProcedureMetadata procedure : procedures) { + children.add(pathFactory.createSegment(procedure.getName())); + } + + return new PathNode(null, nodePath.getParent(), nodePath.getLastSegment(), properties, children); + } catch (JdbcMetadataException se) { + throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetProcedureNames.text(catalogName, schemaName), se); + } finally { + repository.closeConnection(conn); + } + } + + private PathNode procedureNodeFor( List segments ) throws RepositorySourceException { + assert segments != null; + assert segments.size() == 4; + assert PROCEDURES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName()); + + ExecutionContext context = repository.source().getRepositoryContext().getExecutionContext(); + PathFactory pathFactory = context.getValueFactories().getPathFactory(); + PropertyFactory propFactory = context.getPropertyFactory(); + + Path nodePath = pathFactory.createAbsolutePath(segments); + + Connection conn = repository.getConnection(); + String catalogName = segments.get(0).getName().getLocalName(); + if (catalogName.equals(repository.source().getDefaultCatalogName())) catalogName = null; + + String schemaName = segments.get(1).getName().getLocalName(); + if (schemaName.equals(repository.source().getDefaultSchemaName())) schemaName = null; + + String procedureName = segments.get(3).getName().getLocalName(); + + try { + MetadataCollector meta = repository.source().getMetadataCollector(); + + List procedures = meta.getProcedures(conn, catalogName, schemaName, procedureName); + + // Make sure that the table exists in the given catalog and schema + if (procedures.isEmpty()) { + return null; + } + + /* + * Some RDMSes support overloaded procedures and thus can return multiple records for the + * same procedure name in the same catalog and schema (e.g., HSQLDB and the Math.abs procedure). + * + * That means that: + * 1. CollectorMetadata.getProcedures(...) needs to consider overloaded procedures when determining + * the stable order in which the procedures should be returned + * 2. Procedure nodes can have an SNS index > 1 + */ + if (segments.get(3).getIndex() > procedures.size()) { + return null; + } + + ProcedureMetadata procedure = procedures.get(segments.get(3).getIndex() - 1); + + Map properties = new HashMap(); + Name propName; + propName = JcrLexicon.PRIMARY_TYPE; + properties.put(propName, propFactory.create(propName, JcrNtLexicon.UNSTRUCTURED)); + propName = JcrLexicon.MIXIN_TYPES; + properties.put(propName, propFactory.create(propName, JdbcMetadataLexicon.PROCEDURE)); + + if (procedure.getDescription() != null) { + propName = JdbcMetadataLexicon.DESCRIPTION; + properties.put(propName, propFactory.create(propName, procedure.getDescription())); + } + propName = JdbcMetadataLexicon.PROCEDURE_RETURN_TYPE; + properties.put(propName, propFactory.create(propName, procedure.getType())); + + return new PathNode(null, nodePath.getParent(), nodePath.getLastSegment(), properties, Collections.emptyList()); + } catch (JdbcMetadataException se) { + throw new RepositorySourceException( + JdbcMetadataI18n.couldNotGetProcedure.text(catalogName, schemaName, procedureName), + se); + } finally { + repository.closeConnection(conn); + } + } + + private PathNode columnNodeFor( List segments ) throws RepositorySourceException { + assert segments != null; + assert segments.size() == 5; + assert TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName()); + + ExecutionContext context = repository.source().getRepositoryContext().getExecutionContext(); + PathFactory pathFactory = context.getValueFactories().getPathFactory(); + PropertyFactory propFactory = context.getPropertyFactory(); + + Path nodePath = pathFactory.createAbsolutePath(segments); + + Connection conn = repository.getConnection(); + String catalogName = segments.get(0).getName().getLocalName(); + if (catalogName.equals(repository.source().getDefaultCatalogName())) catalogName = null; + + String schemaName = segments.get(1).getName().getLocalName(); + if (schemaName.equals(repository.source().getDefaultSchemaName())) schemaName = null; + + String tableName = segments.get(3).getName().getLocalName(); + String columnName = segments.get(4).getName().getLocalName(); + + try { + MetadataCollector meta = repository.source().getMetadataCollector(); + + List columns = meta.getColumns(conn, catalogName, schemaName, tableName, columnName); + + // Make sure that the column exists in the given table, catalog, and schema + if (columns.isEmpty()) { + return null; + } + + assert columns.size() == 1 : "Duplicate column named " + columnName; + ColumnMetadata column = columns.get(0); + + Map properties = new HashMap(); + Name propName; + propName = JcrLexicon.PRIMARY_TYPE; + properties.put(propName, propFactory.create(propName, JcrNtLexicon.UNSTRUCTURED)); + propName = JcrLexicon.MIXIN_TYPES; + properties.put(propName, propFactory.create(propName, JdbcMetadataLexicon.COLUMN)); + + propName = JdbcMetadataLexicon.JDBC_DATA_TYPE; + properties.put(propName, propFactory.create(propName, column.getJdbcDataType())); + propName = JdbcMetadataLexicon.TYPE_NAME; + properties.put(propName, propFactory.create(propName, column.getTypeName())); + propName = JdbcMetadataLexicon.COLUMN_SIZE; + properties.put(propName, propFactory.create(propName, column.getColumnSize())); + propName = JdbcMetadataLexicon.DECIMAL_DIGITS; + properties.put(propName, propFactory.create(propName, column.getDecimalDigits())); + propName = JdbcMetadataLexicon.RADIX; + properties.put(propName, propFactory.create(propName, column.getRadix())); + if (column.getNullable() != null) { + propName = JdbcMetadataLexicon.NULLABLE; + properties.put(propName, propFactory.create(propName, column.getNullable())); + } + if (column.getDescription() != null) { + propName = JdbcMetadataLexicon.DESCRIPTION; + properties.put(propName, propFactory.create(propName, column.getDescription())); + } + if (column.getDefaultValue() != null) { + propName = JdbcMetadataLexicon.DEFAULT_VALUE; + properties.put(propName, propFactory.create(propName, column.getDefaultValue())); + } + propName = JdbcMetadataLexicon.LENGTH; + properties.put(propName, propFactory.create(propName, column.getLength())); + propName = JdbcMetadataLexicon.ORDINAL_POSITION; + properties.put(propName, propFactory.create(propName, column.getOrdinalPosition())); + if (column.getScopeCatalogName() != null) { + propName = JdbcMetadataLexicon.SCOPE_CATALOG_NAME; + + properties.put(propName, propFactory.create(propName, column.getScopeCatalogName())); + } + if (column.getScopeSchemaName() != null) { + propName = JdbcMetadataLexicon.SCOPE_SCHEMA_NAME; + properties.put(propName, propFactory.create(propName, column.getScopeSchemaName())); + } + if (column.getScopeTableName() != null) { + propName = JdbcMetadataLexicon.SCOPE_TABLE_NAME; + properties.put(propName, propFactory.create(propName, column.getScopeTableName())); + } + if (column.getSourceJdbcDataType() != null) { + propName = JdbcMetadataLexicon.SOURCE_JDBC_DATA_TYPE; + properties.put(propName, propFactory.create(propName, column.getSourceJdbcDataType())); + } + return new PathNode(null, nodePath.getParent(), nodePath.getLastSegment(), properties, + Collections.emptyList()); + } catch (JdbcMetadataException se) { + throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetColumn.text(catalogName, + schemaName, + tableName, + columnName), se); + } finally { + repository.closeConnection(conn); + } + } + + @Override + public PathNode getRootNode() throws RepositorySourceException { + List catalogNames = new LinkedList(); + ExecutionContext context = repository.source().getRepositoryContext().getExecutionContext(); + PathFactory pathFactory = context.getValueFactories().getPathFactory(); + + Connection conn = repository.getConnection(); + try { + MetadataCollector meta = repository.source().getMetadataCollector(); + + for (String catalogName : meta.getCatalogNames(conn)) { + if (catalogName.length() > 0) { + catalogNames.add(pathFactory.createSegment(catalogName)); + } + } + + if (catalogNames.isEmpty()) { + // This database must not support catalogs + catalogNames.add(pathFactory.createSegment(repository.source().getDefaultCatalogName())); + } + + return new PathNode(repository.source().getRootNodeUuidObject(), null, null, repository.rootNodeProperties(), + catalogNames); + } catch (JdbcMetadataException se) { + throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetCatalogNames.text(), se); + } finally { + repository.closeConnection(conn); + } + } + +} Index: extensions/modeshape-connector-jdbc-metadata/src/test/java/org/modeshape/connector/meta/jdbc/JdbcMetadataRepositoryTest.java =================================================================== --- extensions/modeshape-connector-jdbc-metadata/src/test/java/org/modeshape/connector/meta/jdbc/JdbcMetadataRepositoryTest.java (revision 1824) +++ extensions/modeshape-connector-jdbc-metadata/src/test/java/org/modeshape/connector/meta/jdbc/JdbcMetadataRepositoryTest.java (working copy) @@ -37,13 +37,19 @@ import java.sql.Types; import java.util.Map; import java.util.Set; import javax.sql.DataSource; -import org.modeshape.graph.ModeShapeLexicon; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.modeshape.connector.meta.jdbc.JdbcMetadataRepository.JdbcMetadataTransaction; import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.JcrLexicon; import org.modeshape.graph.JcrNtLexicon; +import org.modeshape.graph.Location; +import org.modeshape.graph.ModeShapeLexicon; import org.modeshape.graph.connector.RepositoryContext; -import org.modeshape.graph.connector.path.PathNode; -import org.modeshape.graph.connector.path.PathWorkspace; +import org.modeshape.graph.connector.base.PathNode; import org.modeshape.graph.property.Name; import org.modeshape.graph.property.NameFactory; import org.modeshape.graph.property.Path; @@ -51,11 +57,7 @@ import org.modeshape.graph.property.PathFactory; import org.modeshape.graph.property.Property; import org.modeshape.graph.property.ValueFactory; import org.modeshape.graph.property.Path.Segment; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.MockitoAnnotations; -import org.mockito.Mock; +import org.modeshape.graph.request.InvalidWorkspaceException; public class JdbcMetadataRepositoryTest { @@ -64,7 +66,8 @@ public class JdbcMetadataRepositoryTest { private RepositoryContext repositoryContext; private ExecutionContext context; private JdbcMetadataRepository repository; - private PathWorkspace workspace; + private JdbcMetadataWorkspace workspace; + private JdbcMetadataTransaction txn; private PathFactory pathFactory; private NameFactory nameFactory; private ValueFactory longFactory; @@ -99,7 +102,8 @@ public class JdbcMetadataRepositoryTest { source.getConnection().close(); // Need to call this once to instantiate the repository this.repository = source.repository(); - workspace = repository.getWorkspace(source.getDefaultWorkspaceName()); + txn = repository.startTransaction(context, true); + workspace = txn.getWorkspace(source.getDefaultWorkspaceName(), null); assertThat(workspace, is(notNullValue())); try { @@ -160,19 +164,19 @@ public class JdbcMetadataRepositoryTest { } - @Test + @Test( expected = InvalidWorkspaceException.class ) public void shouldNotReturnInvalidWorkspace() { - workspace = repository.getWorkspace(source.getDefaultWorkspaceName() + "Invalid"); - assertThat(workspace, is(nullValue())); + JdbcMetadataTransaction txn = repository.startTransaction(context, true); + workspace = txn.getWorkspace(source.getDefaultWorkspaceName() + "Invalid", null); } @Test public void shouldReturnRootNode() { Path rootPath = pathFactory.createRootPath(); - PathNode rootNode = workspace.getNode(rootPath); + PathNode rootNode = txn.getNode(workspace, Location.create(rootPath)); Map properties = rootNode.getProperties(); - assertThat(rootNode.getPath(), is(rootPath)); + assertThat(pathFor(rootNode), is(rootPath)); assertThat(properties, is(notNullValue())); assertThat(properties.size(), is(6)); assertThat(nameFor(properties.get(JcrLexicon.PRIMARY_TYPE)), is(ModeShapeLexicon.ROOT)); @@ -182,7 +186,7 @@ public class JdbcMetadataRepositoryTest { assertThat(properties.get(JdbcMetadataLexicon.DATABASE_MAJOR_VERSION), is(notNullValue())); assertThat(properties.get(JdbcMetadataLexicon.DATABASE_MINOR_VERSION), is(notNullValue())); - assertThat(rootNode.getChildSegments().isEmpty(), is(false)); + assertThat(rootNode.getChildren().isEmpty(), is(false)); } @Test @@ -190,26 +194,26 @@ public class JdbcMetadataRepositoryTest { Path rootPath = pathFactory.createRootPath(); PathNode rootNode = workspace.getNode(rootPath); - Segment catalogSegment = rootNode.getChildSegments().get(0); + Segment catalogSegment = rootNode.getChildren().get(0); Path catalogPath = pathFactory.createAbsolutePath(catalogSegment); PathNode catalogNode = workspace.getNode(catalogPath); Map properties = catalogNode.getProperties(); - assertThat(catalogNode.getPath(), is(catalogPath)); + assertThat(pathFor(catalogNode), is(catalogPath)); assertThat(properties, is(notNullValue())); assertThat(properties.size(), is(2)); assertThat(nameFor(properties.get(JcrLexicon.PRIMARY_TYPE)), is(JcrNtLexicon.UNSTRUCTURED)); assertThat(nameFor(properties.get(JcrLexicon.MIXIN_TYPES)), is(JdbcMetadataLexicon.CATALOG)); - assertThat(rootNode.getChildSegments().isEmpty(), is(false)); + assertThat(rootNode.getChildren().isEmpty(), is(false)); } @Test public void shouldNotReturnInvalidCatalogNode() { Path rootPath = pathFactory.createRootPath(); PathNode rootNode = workspace.getNode(rootPath); - Segment catalogSegment = rootNode.getChildSegments().get(0); + Segment catalogSegment = rootNode.getChildren().get(0); Name invalidCatalogName = nameFactory.create(catalogSegment.getName().getLocalName() + "-InvalidCatalog"); Path catalogPath = pathFactory.createAbsolutePath(invalidCatalogName); @@ -221,23 +225,23 @@ public class JdbcMetadataRepositoryTest { Path rootPath = pathFactory.createRootPath(); PathNode rootNode = workspace.getNode(rootPath); - Segment catalogSegment = rootNode.getChildSegments().get(0); + Segment catalogSegment = rootNode.getChildren().get(0); Path catalogPath = pathFactory.createAbsolutePath(catalogSegment); PathNode catalogNode = workspace.getNode(catalogPath); - Segment schemaSegment = catalogNode.getChildSegments().get(0); + Segment schemaSegment = catalogNode.getChildren().get(0); Path schemaPath = pathFactory.createAbsolutePath(catalogSegment, schemaSegment); PathNode schemaNode = workspace.getNode(schemaPath); Map properties = schemaNode.getProperties(); - assertThat(schemaNode.getPath(), is(schemaPath)); + assertThat(pathFor(schemaNode), is(schemaPath)); assertThat(properties, is(notNullValue())); assertThat(properties.size(), is(2)); assertThat(nameFor(properties.get(JcrLexicon.PRIMARY_TYPE)), is(JcrNtLexicon.UNSTRUCTURED)); assertThat(nameFor(properties.get(JcrLexicon.MIXIN_TYPES)), is(JdbcMetadataLexicon.SCHEMA)); - assertThat(rootNode.getChildSegments().isEmpty(), is(false)); + assertThat(rootNode.getChildren().isEmpty(), is(false)); } @Test @@ -245,11 +249,11 @@ public class JdbcMetadataRepositoryTest { Path rootPath = pathFactory.createRootPath(); PathNode rootNode = workspace.getNode(rootPath); - Segment catalogSegment = rootNode.getChildSegments().get(0); + Segment catalogSegment = rootNode.getChildren().get(0); Path catalogPath = pathFactory.createAbsolutePath(catalogSegment); PathNode catalogNode = workspace.getNode(catalogPath); - Segment schemaSegment = catalogNode.getChildSegments().get(0); + Segment schemaSegment = catalogNode.getChildren().get(0); Name invalidSchemaName = nameFactory.create(schemaSegment.getName().getLocalName() + "-InvalidSchema"); Path invalidSchemaPath = pathFactory.createAbsolutePath(catalogSegment.getName(), invalidSchemaName); assertThat(workspace.getNode(invalidSchemaPath), is(nullValue())); @@ -265,13 +269,13 @@ public class JdbcMetadataRepositoryTest { Map properties = tablesNode.getProperties(); - assertThat(tablesNode.getPath(), is(tablesPath)); + assertThat(pathFor(tablesNode), is(tablesPath)); assertThat(properties, is(notNullValue())); assertThat(properties.size(), is(2)); assertThat(nameFor(properties.get(JcrLexicon.PRIMARY_TYPE)), is(JcrNtLexicon.UNSTRUCTURED)); assertThat(nameFor(properties.get(JcrLexicon.MIXIN_TYPES)), is(JdbcMetadataLexicon.TABLES)); - assertThat(tablesNode.getChildSegments().isEmpty(), is(false)); + assertThat(tablesNode.getChildren().isEmpty(), is(false)); } @Test @@ -280,7 +284,7 @@ public class JdbcMetadataRepositoryTest { Path catalogPath = pathFactory.createAbsolutePath(catalogSegment); PathNode catalogNode = workspace.getNode(catalogPath); - Segment schemaSegment = catalogNode.getChildSegments().get(0); + Segment schemaSegment = catalogNode.getChildren().get(0); Name invalidSchemaName = nameFactory.create(schemaSegment.getName().getLocalName() + "-InvalidSchema"); Path invalidSchemaPath = pathFactory.createAbsolutePath(catalogSegment.getName(), invalidSchemaName, @@ -301,14 +305,14 @@ public class JdbcMetadataRepositoryTest { Map properties = tableNode.getProperties(); - assertThat(tableNode.getPath(), is(tablePath)); + assertThat(pathFor(tableNode), is(tablePath)); assertThat(properties, is(notNullValue())); assertThat(properties.size() >= 2, is(true)); assertThat(properties.size() <= 9, is(true)); assertThat(nameFor(properties.get(JcrLexicon.PRIMARY_TYPE)), is(JcrNtLexicon.UNSTRUCTURED)); assertThat(nameFor(properties.get(JcrLexicon.MIXIN_TYPES)), is(JdbcMetadataLexicon.TABLE)); - assertThat(tableNode.getChildSegments().size(), is(4)); + assertThat(tableNode.getChildren().size(), is(4)); } @Test @@ -317,7 +321,7 @@ public class JdbcMetadataRepositoryTest { Path catalogPath = pathFactory.createAbsolutePath(catalogSegment); PathNode catalogNode = workspace.getNode(catalogPath); - Segment schemaSegment = catalogNode.getChildSegments().get(0); + Segment schemaSegment = catalogNode.getChildren().get(0); Name invalidSchemaName = nameFactory.create(schemaSegment.getName().getLocalName() + "-InvalidSchema"); Path invalidSchemaPath = pathFactory.createAbsolutePath(catalogSegment.getName(), invalidSchemaName, @@ -354,7 +358,7 @@ public class JdbcMetadataRepositoryTest { Map properties = columnNode.getProperties(); - assertThat(columnNode.getPath(), is(columnPath)); + assertThat(pathFor(columnNode), is(columnPath)); assertThat(properties, is(notNullValue())); assertThat(properties.size() >= 9, is(true)); assertThat(properties.size() <= 16, is(true)); @@ -372,7 +376,7 @@ public class JdbcMetadataRepositoryTest { assertThat(longFor(properties.get(JdbcMetadataLexicon.ORDINAL_POSITION)), is(4L)); // Some DBMSes don't have any procedures in the default schema - // assertThat(columnNode.getChildSegments().isEmpty(), is(true)); + // assertThat(columnNode.getChildren().isEmpty(), is(true)); } @Test @@ -381,7 +385,7 @@ public class JdbcMetadataRepositoryTest { Path catalogPath = pathFactory.createAbsolutePath(catalogSegment); PathNode catalogNode = workspace.getNode(catalogPath); - Segment schemaSegment = catalogNode.getChildSegments().get(0); + Segment schemaSegment = catalogNode.getChildren().get(0); Name invalidSchemaName = nameFactory.create(schemaSegment.getName().getLocalName() + "-InvalidSchema"); Path invalidSchemaPath = pathFactory.createAbsolutePath(catalogSegment.getName(), invalidSchemaName, @@ -422,14 +426,14 @@ public class JdbcMetadataRepositoryTest { Map properties = proceduresNode.getProperties(); - assertThat(proceduresNode.getPath(), is(proceduresPath)); + assertThat(pathFor(proceduresNode), is(proceduresPath)); assertThat(properties, is(notNullValue())); assertThat(properties.size(), is(2)); assertThat(nameFor(properties.get(JcrLexicon.PRIMARY_TYPE)), is(JcrNtLexicon.UNSTRUCTURED)); assertThat(nameFor(properties.get(JcrLexicon.MIXIN_TYPES)), is(JdbcMetadataLexicon.PROCEDURES)); // Not all schemas will have stored procs - // assertThat(proceduresNode.getChildSegments().isEmpty(), is(false)); + // assertThat(proceduresNode.getChildren().isEmpty(), is(false)); } @Test @@ -437,11 +441,11 @@ public class JdbcMetadataRepositoryTest { Path rootPath = pathFactory.createRootPath(); PathNode rootNode = workspace.getNode(rootPath); - Segment catalogSegment = rootNode.getChildSegments().get(0); + Segment catalogSegment = rootNode.getChildren().get(0); Path catalogPath = pathFactory.createAbsolutePath(catalogSegment); PathNode catalogNode = workspace.getNode(catalogPath); - Segment schemaSegment = catalogNode.getChildSegments().get(0); + Segment schemaSegment = catalogNode.getChildren().get(0); Name invalidSchemaName = nameFactory.create(schemaSegment.getName().getLocalName() + "-InvalidSchema"); Path invalidSchemaPath = pathFactory.createAbsolutePath(catalogSegment.getName(), invalidSchemaName, @@ -449,6 +453,17 @@ public class JdbcMetadataRepositoryTest { assertThat(workspace.getNode(invalidSchemaPath), is(nullValue())); } + private Path pathFor( PathNode node ) { + if (node == null) { + return null; + } + + if (node.getParent() == null) { + return pathFactory.createRootPath(); + } + + return pathFactory.create(node.getParent(), node.getName()); + } private Name nameFor( Property property ) { if (property == null) { return null; Index: extensions/modeshape-connector-jdbc-metadata/src/test/java/org/modeshape/connector/meta/jdbc/TestEnvironment.java =================================================================== --- extensions/modeshape-connector-jdbc-metadata/src/test/java/org/modeshape/connector/meta/jdbc/TestEnvironment.java (revision 1824) +++ extensions/modeshape-connector-jdbc-metadata/src/test/java/org/modeshape/connector/meta/jdbc/TestEnvironment.java (working copy) @@ -80,7 +80,7 @@ public class TestEnvironment { if (isValue(value)) source.setRetryLimit(Integer.parseInt(value)); value = properties.getProperty("jpaSource.rootNodeUuid"); - if (isValue(value)) source.setRootNodeUuid(value); + if (isValue(value)) source.setRootNodeUuidObject(value); value = properties.getProperty("metadata.collectorClassName"); if (isValue(value)) source.setMetadataCollectorClassName(value); Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/base/PathTransaction.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/connector/base/PathTransaction.java (revision 1824) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/base/PathTransaction.java (working copy) @@ -129,6 +129,20 @@ public abstract class PathTransaction changedOrAddedNodes = new TreeMap(); + private final TreeMap changedOrAddedNodes = new TreeMap(); private final TreeMap movedNodes = new TreeMap(); private final Set removedNodes = new HashSet(); private final List> commands = new LinkedList>(); @@ -746,6 +760,16 @@ public abstract class PathTransaction