View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors. 
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   *
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.connector.meta.jdbc;
25  
26  import java.sql.Connection;
27  import java.sql.DatabaseMetaData;
28  import java.sql.SQLException;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.LinkedList;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.UUID;
39  import net.jcip.annotations.ThreadSafe;
40  import org.modeshape.common.util.Logger;
41  import org.modeshape.graph.ModeShapeLexicon;
42  import org.modeshape.graph.ExecutionContext;
43  import org.modeshape.graph.JcrLexicon;
44  import org.modeshape.graph.JcrNtLexicon;
45  import org.modeshape.graph.connector.LockFailedException;
46  import org.modeshape.graph.connector.RepositorySourceException;
47  import org.modeshape.graph.connector.path.DefaultPathNode;
48  import org.modeshape.graph.connector.path.PathNode;
49  import org.modeshape.graph.connector.path.PathRepository;
50  import org.modeshape.graph.connector.path.PathWorkspace;
51  import org.modeshape.graph.connector.path.cache.WorkspaceCache;
52  import org.modeshape.graph.property.Name;
53  import org.modeshape.graph.property.NameFactory;
54  import org.modeshape.graph.property.Path;
55  import org.modeshape.graph.property.PathFactory;
56  import org.modeshape.graph.property.PathNotFoundException;
57  import org.modeshape.graph.property.Property;
58  import org.modeshape.graph.property.PropertyFactory;
59  import org.modeshape.graph.property.Path.Segment;
60  import org.modeshape.graph.query.QueryResults;
61  import org.modeshape.graph.request.AccessQueryRequest;
62  import org.modeshape.graph.request.LockBranchRequest.LockScope;
63  
64  @ThreadSafe
65  public class JdbcMetadataRepository extends PathRepository {
66  
67      public final static String TABLES_SEGMENT_NAME = "tables";
68      public final static String PROCEDURES_SEGMENT_NAME = "procedures";
69  
70      private static final Logger LOGGER = Logger.getLogger(JdbcMetadataRepository.class);
71      private final JdbcMetadataSource source;
72      private Map<Name, Property> rootNodeProperties;
73      private String databaseProductName;
74      private String databaseProductVersion;
75      private int databaseMajorVersion;
76      private int databaseMinorVersion;
77  
78      public JdbcMetadataRepository( JdbcMetadataSource source ) {
79          super(source);
80          this.source = source;
81          initialize();
82      }
83  
84      @Override
85      protected synchronized void initialize() {
86          if (!this.workspaces.isEmpty()) return;
87  
88          String defaultWorkspaceName = getDefaultWorkspaceName();
89          this.workspaces.put(defaultWorkspaceName, new JdbcMetadataWorkspace(defaultWorkspaceName));
90  
91          ExecutionContext context = source.getRepositoryContext().getExecutionContext();
92          PropertyFactory propFactory = context.getPropertyFactory();
93          this.rootNodeProperties = new HashMap<Name, Property>();
94  
95          Connection conn = getConnection();
96          try {
97              Name propName;
98              DatabaseMetaData dmd = conn.getMetaData();
99  
100             databaseProductName = dmd.getDatabaseProductName();
101             databaseProductVersion = dmd.getDatabaseProductVersion();
102             databaseMajorVersion = dmd.getDatabaseMajorVersion();
103             databaseMinorVersion = dmd.getDatabaseMinorVersion();
104 
105             propName = JdbcMetadataLexicon.DATABASE_PRODUCT_NAME;
106             rootNodeProperties.put(propName, propFactory.create(propName, databaseProductName));
107             propName = JdbcMetadataLexicon.DATABASE_PRODUCT_VERSION;
108             rootNodeProperties.put(propName, propFactory.create(propName, databaseProductVersion));
109             propName = JdbcMetadataLexicon.DATABASE_MAJOR_VERSION;
110             rootNodeProperties.put(propName, propFactory.create(propName, databaseMajorVersion));
111             propName = JdbcMetadataLexicon.DATABASE_MINOR_VERSION;
112             rootNodeProperties.put(propName, propFactory.create(propName, databaseMinorVersion));
113 
114             rootNodeProperties.put(JcrLexicon.PRIMARY_TYPE, propFactory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.ROOT));
115             rootNodeProperties.put(JcrLexicon.MIXIN_TYPES, propFactory.create(JcrLexicon.MIXIN_TYPES,
116                                                                               JdbcMetadataLexicon.DATABASE_ROOT));
117 
118             rootNodeProperties = Collections.unmodifiableMap(rootNodeProperties);
119         } catch (SQLException se) {
120             throw new IllegalStateException(JdbcMetadataI18n.couldNotGetDatabaseMetadata.text(), se);
121         } finally {
122             closeConnection(conn);
123         }
124     }
125 
126     public WorkspaceCache getCache( String workspaceName ) {
127         return source.getPathRepositoryCache().getCache(workspaceName);
128     }
129 
130     Connection getConnection() {
131         try {
132             return source.getDataSource().getConnection();
133         } catch (SQLException se) {
134             throw new IllegalStateException(JdbcMetadataI18n.errorObtainingConnection.text(), se);
135         }
136     }
137 
138     void closeConnection( Connection connection ) {
139         assert connection != null;
140         try {
141             connection.close();
142         } catch (SQLException se) {
143             LOGGER.error(se, JdbcMetadataI18n.errorClosingConnection);
144         }
145     }
146 
147     @ThreadSafe
148     @SuppressWarnings( "synthetic-access" )
149     private class JdbcMetadataWorkspace implements PathWorkspace {
150 
151         private final String name;
152         private final WorkspaceCache cache;
153 
154         JdbcMetadataWorkspace( String name ) {
155             this.name = name;
156             cache = getCache(name);
157         }
158 
159         public Path getLowestExistingPath( Path path ) {
160             PathFactory pathFactory = source.getRepositoryContext().getExecutionContext().getValueFactories().getPathFactory();
161 
162             Path lastWorkingPath = pathFactory.createRootPath();
163 
164             for (Segment segment : path.getSegmentsList()) {
165                 Path newPathToTry = pathFactory.create(lastWorkingPath, segment);
166 
167                 try {
168                     getNode(newPathToTry);
169                     lastWorkingPath = newPathToTry;
170                 } catch (PathNotFoundException pnfe) {
171                     return lastWorkingPath;
172                 }
173             }
174 
175             // If we got here, someone called getLowestExistingPath on a path that was invalid but now isn't.
176             return path;
177         }
178 
179         public String getName() {
180             return this.name;
181         }
182 
183         public PathNode getNode( Path path ) {
184             assert path != null;
185 
186             PathNode node = cache.get(path);
187             if (node != null) return node;
188 
189             List<Segment> segments = path.getSegmentsList();
190             switch (segments.size()) {
191                 case 0:
192                     node = getRoot();
193                     break;
194                 case 1:
195                     node = catalogNodeFor(segments);
196                     break;
197                 case 2:
198                     node = schemaNodeFor(segments);
199                     break;
200                 case 3:
201                     if (TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName())) {
202                         node = tablesNodeFor(segments);
203                     } else if (PROCEDURES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName())) {
204                         node = proceduresNodeFor(segments);
205                     }
206                     break;
207                 case 4:
208                     if (TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName())) {
209                         node = tableNodeFor(segments);
210                     } else if (PROCEDURES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName())) {
211                         node = procedureNodeFor(segments);
212                     }
213                     break;
214                 case 5:
215                     if (TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName())) {
216                         node = columnNodeFor(segments);
217                     }
218                     break;
219                 default:
220                     return null;
221             }
222 
223             if (node != null) cache.set(node);
224             return node;
225         }
226 
227         private PathNode catalogNodeFor( List<Segment> segments ) throws RepositorySourceException {
228             assert segments != null;
229             assert segments.size() == 1;
230 
231             List<Segment> schemaNames = new LinkedList<Segment>();
232             ExecutionContext context = source.getRepositoryContext().getExecutionContext();
233             PathFactory pathFactory = context.getValueFactories().getPathFactory();
234             PropertyFactory propFactory = context.getPropertyFactory();
235 
236             Path nodePath = pathFactory.createAbsolutePath(segments);
237 
238             Connection conn = getConnection();
239             String catalogName = segments.get(0).getName().getLocalName();
240 
241             try {
242                 MetadataCollector meta = source.getMetadataCollector();
243                 if (catalogName.equals(source.getDefaultCatalogName())) catalogName = null;
244 
245                 // Make sure that this is a valid catalog for this database
246                 List<String> catalogNames = meta.getCatalogNames(conn);
247 
248                 /*
249                  * If a "real" (not default) catalog name is provided but it is not a valid
250                  * catalog name for this database OR if the default catalog name is being used
251                  * but this database uses real catalog names, then no catalog with that name exists.
252                  * 
253                  * This gets complicated by the fact that some DBMSes use an empty string for a catalog
254                  * which also gets mapped to the default catalog name in our system
255                  */
256                 boolean catalogMatchesDefaultName = catalogNames.isEmpty() || catalogNames.contains("");
257 
258                 if ((catalogName != null && !catalogNames.contains(catalogName))
259                     || (catalogName == null && !catalogMatchesDefaultName)) {
260                     return null;
261                 }
262 
263                 List<String> schemaNamesFromMeta = new ArrayList<String>(meta.getSchemaNames(conn, catalogName));
264 
265                 for (String schemaName : schemaNamesFromMeta) {
266                     if (schemaName.length() > 0) {
267                         schemaNames.add(pathFactory.createSegment(schemaName));
268                     }
269                 }
270 
271                 if (schemaNames.isEmpty()) {
272                     schemaNames.add(pathFactory.createSegment(source.getDefaultSchemaName()));
273                 }
274 
275                 Map<Name, Property> properties = new HashMap<Name, Property>();
276                 properties.put(JcrLexicon.PRIMARY_TYPE, propFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.UNSTRUCTURED));
277                 properties.put(JcrLexicon.MIXIN_TYPES, propFactory.create(JcrLexicon.MIXIN_TYPES, JdbcMetadataLexicon.CATALOG));
278 
279                 return new DefaultPathNode(nodePath, null, properties, schemaNames);
280             } catch (JdbcMetadataException se) {
281                 throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetSchemaNames.text(catalogName), se);
282             } finally {
283                 closeConnection(conn);
284             }
285         }
286 
287         private PathNode schemaNodeFor( List<Segment> segments ) throws RepositorySourceException {
288             assert segments != null;
289             assert segments.size() == 2;
290 
291             ExecutionContext context = source.getRepositoryContext().getExecutionContext();
292             PathFactory pathFactory = context.getValueFactories().getPathFactory();
293             PropertyFactory propFactory = context.getPropertyFactory();
294 
295             Path nodePath = pathFactory.createAbsolutePath(segments);
296 
297             Connection conn = getConnection();
298             String catalogName = segments.get(0).getName().getLocalName();
299             if (catalogName.equals(source.getDefaultCatalogName())) catalogName = null;
300 
301             String schemaName = segments.get(1).getName().getLocalName();
302             if (schemaName.equals(source.getDefaultSchemaName())) schemaName = null;
303 
304             try {
305                 MetadataCollector meta = source.getMetadataCollector();
306 
307                 // Make sure that the schema exists in the given catalog
308                 List<String> schemaNames = meta.getSchemaNames(conn, catalogName);
309 
310                 /*
311                  * If a "real" (not default) catalog name is provided but it is not a valid
312                  * catalog name for this database OR if the default catalog name is being used
313                  * but this database uses real catalog names, then no catalog with that name exists.
314                  */
315                 if ((schemaName != null && !schemaNames.contains(schemaName)) || (schemaName == null && !schemaNames.isEmpty())) {
316                     return null;
317                 }
318 
319                 Map<Name, Property> properties = new HashMap<Name, Property>();
320                 properties.put(JcrLexicon.PRIMARY_TYPE, propFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.UNSTRUCTURED));
321                 properties.put(JcrLexicon.MIXIN_TYPES, propFactory.create(JcrLexicon.MIXIN_TYPES, JdbcMetadataLexicon.SCHEMA));
322 
323                 Segment[] children = new Segment[] {pathFactory.createSegment(TABLES_SEGMENT_NAME),
324                     pathFactory.createSegment(PROCEDURES_SEGMENT_NAME)};
325 
326                 return new DefaultPathNode(nodePath, null, properties, Arrays.asList(children));
327             } catch (JdbcMetadataException se) {
328                 throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetSchemaNames.text(catalogName), se);
329             } finally {
330                 closeConnection(conn);
331             }
332         }
333 
334         private PathNode tablesNodeFor( List<Segment> segments ) throws RepositorySourceException {
335             assert segments != null;
336             assert segments.size() == 3;
337             assert TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName());
338 
339             ExecutionContext context = source.getRepositoryContext().getExecutionContext();
340             PathFactory pathFactory = context.getValueFactories().getPathFactory();
341             PropertyFactory propFactory = context.getPropertyFactory();
342 
343             Path nodePath = pathFactory.createAbsolutePath(segments);
344 
345             Connection conn = getConnection();
346             String catalogName = segments.get(0).getName().getLocalName();
347             if (catalogName.equals(source.getDefaultCatalogName())) catalogName = null;
348 
349             String schemaName = segments.get(1).getName().getLocalName();
350             if (schemaName.equals(source.getDefaultSchemaName())) schemaName = null;
351 
352             try {
353                 MetadataCollector meta = source.getMetadataCollector();
354 
355                 // Make sure that the schema exists in the given catalog
356                 List<String> schemaNames = meta.getSchemaNames(conn, catalogName);
357 
358                 /*
359                  * If a "real" (not default) catalog name is provided but it is not a valid
360                  * catalog name for this database OR if the default catalog name is being used
361                  * but this database uses real catalog names, then no catalog with that name exists.
362                  */
363                 if ((schemaName != null && !schemaNames.contains(schemaName)) || (schemaName == null && !schemaNames.isEmpty())) {
364                     return null;
365                 }
366 
367                 Map<Name, Property> properties = new HashMap<Name, Property>();
368                 properties.put(JcrLexicon.PRIMARY_TYPE, propFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.UNSTRUCTURED));
369                 properties.put(JcrLexicon.MIXIN_TYPES, propFactory.create(JcrLexicon.MIXIN_TYPES, JdbcMetadataLexicon.TABLES));
370 
371                 List<TableMetadata> tables = meta.getTables(conn, catalogName, schemaName, null);
372                 List<Segment> children = new ArrayList<Segment>(tables.size());
373 
374                 for (TableMetadata table : tables) {
375                     children.add(pathFactory.createSegment(table.getName()));
376                 }
377 
378                 return new DefaultPathNode(nodePath, null, properties, children);
379             } catch (JdbcMetadataException se) {
380                 throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetTableNames.text(catalogName, schemaName), se);
381             } finally {
382                 closeConnection(conn);
383             }
384         }
385 
386         private PathNode tableNodeFor( List<Segment> segments ) throws RepositorySourceException {
387             assert segments != null;
388             assert segments.size() == 4;
389             assert TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName());
390 
391             ExecutionContext context = source.getRepositoryContext().getExecutionContext();
392             PathFactory pathFactory = context.getValueFactories().getPathFactory();
393             PropertyFactory propFactory = context.getPropertyFactory();
394 
395             Path nodePath = pathFactory.createAbsolutePath(segments);
396 
397             Connection conn = getConnection();
398             String catalogName = segments.get(0).getName().getLocalName();
399             if (catalogName.equals(source.getDefaultCatalogName())) catalogName = null;
400 
401             String schemaName = segments.get(1).getName().getLocalName();
402             if (schemaName.equals(source.getDefaultSchemaName())) schemaName = null;
403 
404             String tableName = segments.get(3).getName().getLocalName();
405 
406             try {
407                 MetadataCollector meta = source.getMetadataCollector();
408 
409                 List<TableMetadata> tables = meta.getTables(conn, catalogName, schemaName, tableName);
410 
411                 // Make sure that the table exists in the given catalog and schema
412                 if (tables.isEmpty()) {
413                     return null;
414                 }
415                 assert tables.size() == 1;
416                 TableMetadata table = tables.get(0);
417 
418                 Map<Name, Property> properties = new HashMap<Name, Property>();
419                 Name propName;
420                 propName = JcrLexicon.PRIMARY_TYPE;
421                 properties.put(propName, propFactory.create(propName, JcrNtLexicon.UNSTRUCTURED));
422                 propName = JcrLexicon.MIXIN_TYPES;
423                 properties.put(propName, propFactory.create(propName, JdbcMetadataLexicon.TABLE));
424 
425                 if (table.getType() != null) {
426                     propName = JdbcMetadataLexicon.TABLE_TYPE;
427                     properties.put(propName, propFactory.create(propName, table.getType()));
428                 }
429                 if (table.getDescription() != null) {
430                     propName = JdbcMetadataLexicon.DESCRIPTION;
431                     properties.put(propName, propFactory.create(propName, table.getDescription()));
432                 }
433                 if (table.getTypeCatalogName() != null) {
434                     propName = JdbcMetadataLexicon.TYPE_CATALOG_NAME;
435                     properties.put(propName, propFactory.create(propName, table.getTypeCatalogName()));
436                 }
437                 if (table.getTypeSchemaName() != null) {
438                     propName = JdbcMetadataLexicon.TYPE_SCHEMA_NAME;
439                     properties.put(propName, propFactory.create(propName, table.getTypeSchemaName()));
440                 }
441                 if (table.getTypeName() != null) {
442                     propName = JdbcMetadataLexicon.TYPE_NAME;
443                     properties.put(propName, propFactory.create(propName, table.getTypeName()));
444                 }
445                 if (table.getSelfReferencingColumnName() != null) {
446                     propName = JdbcMetadataLexicon.SELF_REFERENCING_COLUMN_NAME;
447                     properties.put(propName, propFactory.create(propName, table.getSelfReferencingColumnName()));
448                 }
449                 if (table.getReferenceGenerationStrategyName() != null) {
450                     propName = JdbcMetadataLexicon.REFERENCE_GENERATION_STRATEGY_NAME;
451                     properties.put(propName, propFactory.create(propName, table.getReferenceGenerationStrategyName()));
452                 }
453 
454                 List<ColumnMetadata> columns = meta.getColumns(conn, catalogName, schemaName, tableName, null);
455                 List<Segment> children = new ArrayList<Segment>(columns.size());
456 
457                 for (ColumnMetadata column : columns) {
458                     children.add(pathFactory.createSegment(column.getName()));
459                 }
460 
461                 return new DefaultPathNode(nodePath, null, properties, children);
462             } catch (JdbcMetadataException se) {
463                 throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetTable.text(catalogName, schemaName, tableName),
464                                                     se);
465             } finally {
466                 closeConnection(conn);
467             }
468         }
469 
470         private PathNode proceduresNodeFor( List<Segment> segments ) throws RepositorySourceException {
471             assert segments != null;
472             assert segments.size() == 3;
473             assert PROCEDURES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName());
474 
475             ExecutionContext context = source.getRepositoryContext().getExecutionContext();
476             PathFactory pathFactory = context.getValueFactories().getPathFactory();
477             PropertyFactory propFactory = context.getPropertyFactory();
478 
479             Path nodePath = pathFactory.createAbsolutePath(segments);
480 
481             Connection conn = getConnection();
482             String catalogName = segments.get(0).getName().getLocalName();
483             if (catalogName.equals(source.getDefaultCatalogName())) catalogName = null;
484 
485             String schemaName = segments.get(1).getName().getLocalName();
486             if (schemaName.equals(source.getDefaultSchemaName())) schemaName = null;
487 
488             try {
489                 MetadataCollector meta = source.getMetadataCollector();
490 
491                 // Make sure that the schema exists in the given catalog
492                 List<String> schemaNames = meta.getSchemaNames(conn, catalogName);
493 
494                 /*
495                  * If a "real" (not default) catalog name is provided but it is not a valid
496                  * catalog name for this database OR if the default catalog name is being used
497                  * but this database uses real catalog names, then no catalog with that name exists.
498                  */
499                 if ((schemaName != null && !schemaNames.contains(schemaName)) || (schemaName == null && !schemaNames.isEmpty())) {
500                     return null;
501                 }
502 
503                 Map<Name, Property> properties = new HashMap<Name, Property>();
504                 properties.put(JcrLexicon.PRIMARY_TYPE, propFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.UNSTRUCTURED));
505                 properties.put(JcrLexicon.MIXIN_TYPES, propFactory.create(JcrLexicon.MIXIN_TYPES, JdbcMetadataLexicon.PROCEDURES));
506 
507                 List<ProcedureMetadata> procedures = meta.getProcedures(conn, catalogName, schemaName, null);
508                 List<Segment> children = new ArrayList<Segment>(procedures.size());
509 
510                 for (ProcedureMetadata procedure : procedures) {
511                     children.add(pathFactory.createSegment(procedure.getName()));
512                 }
513 
514                 return new DefaultPathNode(nodePath, null, properties, children);
515             } catch (JdbcMetadataException se) {
516                 throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetProcedureNames.text(catalogName, schemaName), se);
517             } finally {
518                 closeConnection(conn);
519             }
520         }
521 
522         private PathNode procedureNodeFor( List<Segment> segments ) throws RepositorySourceException {
523             assert segments != null;
524             assert segments.size() == 4;
525             assert PROCEDURES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName());
526 
527             ExecutionContext context = source.getRepositoryContext().getExecutionContext();
528             PathFactory pathFactory = context.getValueFactories().getPathFactory();
529             PropertyFactory propFactory = context.getPropertyFactory();
530 
531             Path nodePath = pathFactory.createAbsolutePath(segments);
532 
533             Connection conn = getConnection();
534             String catalogName = segments.get(0).getName().getLocalName();
535             if (catalogName.equals(source.getDefaultCatalogName())) catalogName = null;
536 
537             String schemaName = segments.get(1).getName().getLocalName();
538             if (schemaName.equals(source.getDefaultSchemaName())) schemaName = null;
539 
540             String procedureName = segments.get(3).getName().getLocalName();
541 
542             try {
543                 MetadataCollector meta = source.getMetadataCollector();
544 
545                 List<ProcedureMetadata> procedures = meta.getProcedures(conn, catalogName, schemaName, procedureName);
546 
547                 // Make sure that the table exists in the given catalog and schema
548                 if (procedures.isEmpty()) {
549                     return null;
550                 }
551 
552                 /*
553                  * Some RDMSes support overloaded procedures and thus can return multiple records for the
554                  * same procedure name in the same catalog and schema (e.g., HSQLDB and the Math.abs procedure).
555                  * 
556                  * That means that:
557                  *  1. CollectorMetadata.getProcedures(...) needs to consider overloaded procedures when determining
558                  *     the stable order in which the procedures should be returned
559                  *  2. Procedure nodes can have an SNS index > 1  
560                  */
561                 if (segments.get(3).getIndex() > procedures.size()) {
562                     return null;
563                 }
564 
565                 ProcedureMetadata procedure = procedures.get(segments.get(3).getIndex() - 1);
566 
567                 Map<Name, Property> properties = new HashMap<Name, Property>();
568                 Name propName;
569                 propName = JcrLexicon.PRIMARY_TYPE;
570                 properties.put(propName, propFactory.create(propName, JcrNtLexicon.UNSTRUCTURED));
571                 propName = JcrLexicon.MIXIN_TYPES;
572                 properties.put(propName, propFactory.create(propName, JdbcMetadataLexicon.PROCEDURE));
573 
574                 if (procedure.getDescription() != null) {
575                     propName = JdbcMetadataLexicon.DESCRIPTION;
576                     properties.put(propName, propFactory.create(propName, procedure.getDescription()));
577                 }
578                 propName = JdbcMetadataLexicon.PROCEDURE_RETURN_TYPE;
579                 properties.put(propName, propFactory.create(propName, procedure.getType()));
580 
581                 return new DefaultPathNode(nodePath, null, properties, Collections.<Segment>emptyList());
582             } catch (JdbcMetadataException se) {
583                 throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetProcedure.text(catalogName,
584                                                                                                schemaName,
585                                                                                                procedureName), se);
586             } finally {
587                 closeConnection(conn);
588             }
589         }
590 
591         private PathNode columnNodeFor( List<Segment> segments ) throws RepositorySourceException {
592             assert segments != null;
593             assert segments.size() == 5;
594             assert TABLES_SEGMENT_NAME.equals(segments.get(2).getName().getLocalName());
595 
596             ExecutionContext context = source.getRepositoryContext().getExecutionContext();
597             PathFactory pathFactory = context.getValueFactories().getPathFactory();
598             PropertyFactory propFactory = context.getPropertyFactory();
599 
600             Path nodePath = pathFactory.createAbsolutePath(segments);
601 
602             Connection conn = getConnection();
603             String catalogName = segments.get(0).getName().getLocalName();
604             if (catalogName.equals(source.getDefaultCatalogName())) catalogName = null;
605 
606             String schemaName = segments.get(1).getName().getLocalName();
607             if (schemaName.equals(source.getDefaultSchemaName())) schemaName = null;
608 
609             String tableName = segments.get(3).getName().getLocalName();
610             String columnName = segments.get(4).getName().getLocalName();
611 
612             try {
613                 MetadataCollector meta = source.getMetadataCollector();
614 
615                 List<ColumnMetadata> columns = meta.getColumns(conn, catalogName, schemaName, tableName, columnName);
616 
617                 // Make sure that the column exists in the given table, catalog, and schema
618                 if (columns.isEmpty()) {
619                     return null;
620                 }
621 
622                 assert columns.size() == 1 : "Duplicate column named " + columnName;
623                 ColumnMetadata column = columns.get(0);
624 
625                 Map<Name, Property> properties = new HashMap<Name, Property>();
626                 Name propName;
627                 propName = JcrLexicon.PRIMARY_TYPE;
628                 properties.put(propName, propFactory.create(propName, JcrNtLexicon.UNSTRUCTURED));
629                 propName = JcrLexicon.MIXIN_TYPES;
630                 properties.put(propName, propFactory.create(propName, JdbcMetadataLexicon.COLUMN));
631 
632                 propName = JdbcMetadataLexicon.JDBC_DATA_TYPE;
633                 properties.put(propName, propFactory.create(propName, column.getJdbcDataType()));
634                 propName = JdbcMetadataLexicon.TYPE_NAME;
635                 properties.put(propName, propFactory.create(propName, column.getTypeName()));
636                 propName = JdbcMetadataLexicon.COLUMN_SIZE;
637                 properties.put(propName, propFactory.create(propName, column.getColumnSize()));
638                 propName = JdbcMetadataLexicon.DECIMAL_DIGITS;
639                 properties.put(propName, propFactory.create(propName, column.getDecimalDigits()));
640                 propName = JdbcMetadataLexicon.RADIX;
641                 properties.put(propName, propFactory.create(propName, column.getRadix()));
642                 if (column.getNullable() != null) {
643                     propName = JdbcMetadataLexicon.NULLABLE;
644                     properties.put(propName, propFactory.create(propName, column.getNullable()));
645                 }
646                 if (column.getDescription() != null) {
647                     propName = JdbcMetadataLexicon.DESCRIPTION;
648                     properties.put(propName, propFactory.create(propName, column.getDescription()));
649                 }
650                 if (column.getDefaultValue() != null) {
651                     propName = JdbcMetadataLexicon.DEFAULT_VALUE;
652                     properties.put(propName, propFactory.create(propName, column.getDefaultValue()));
653                 }
654                 propName = JdbcMetadataLexicon.LENGTH;
655                 properties.put(propName, propFactory.create(propName, column.getLength()));
656                 propName = JdbcMetadataLexicon.ORDINAL_POSITION;
657                 properties.put(propName, propFactory.create(propName, column.getOrdinalPosition()));
658                 if (column.getScopeCatalogName() != null) {
659                     propName = JdbcMetadataLexicon.SCOPE_CATALOG_NAME;
660 
661                     properties.put(propName, propFactory.create(propName, column.getScopeCatalogName()));
662                 }
663                 if (column.getScopeSchemaName() != null) {
664                     propName = JdbcMetadataLexicon.SCOPE_SCHEMA_NAME;
665                     properties.put(propName, propFactory.create(propName, column.getScopeSchemaName()));
666                 }
667                 if (column.getScopeTableName() != null) {
668                     propName = JdbcMetadataLexicon.SCOPE_TABLE_NAME;
669                     properties.put(propName, propFactory.create(propName, column.getScopeTableName()));
670                 }
671                 if (column.getSourceJdbcDataType() != null) {
672                     propName = JdbcMetadataLexicon.SOURCE_JDBC_DATA_TYPE;
673                     properties.put(propName, propFactory.create(propName, column.getSourceJdbcDataType()));
674                 }
675                 return new DefaultPathNode(nodePath, null, properties, Collections.<Segment>emptyList());
676             } catch (JdbcMetadataException se) {
677                 throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetColumn.text(catalogName,
678                                                                                             schemaName,
679                                                                                             tableName,
680                                                                                             columnName), se);
681             } finally {
682                 closeConnection(conn);
683             }
684         }
685 
686         public PathNode getRoot() throws RepositorySourceException {
687             List<Segment> catalogNames = new LinkedList<Segment>();
688             ExecutionContext context = source.getRepositoryContext().getExecutionContext();
689             PathFactory pathFactory = context.getValueFactories().getPathFactory();
690 
691             Connection conn = getConnection();
692             try {
693                 MetadataCollector meta = source.getMetadataCollector();
694 
695                 for (String catalogName : meta.getCatalogNames(conn)) {
696                     if (catalogName.length() > 0) {
697                         catalogNames.add(pathFactory.createSegment(catalogName));
698                     }
699                 }
700 
701                 if (catalogNames.isEmpty()) {
702                     // This database must not support catalogs
703                     catalogNames.add(pathFactory.createSegment(source.getDefaultCatalogName()));
704                 }
705 
706                 return new RootNode(catalogNames);
707             } catch (JdbcMetadataException se) {
708                 throw new RepositorySourceException(JdbcMetadataI18n.couldNotGetCatalogNames.text(), se);
709             } finally {
710                 closeConnection(conn);
711             }
712         }
713 
714         /**
715          * This connector does not support connector-level, persistent locking of nodes.
716          * 
717          * @param node
718          * @param lockScope
719          * @param lockTimeoutInMillis
720          * @throws LockFailedException
721          */
722         public void lockNode( PathNode node,
723                               LockScope lockScope,
724                               long lockTimeoutInMillis ) throws LockFailedException {
725             // Locking is not supported by this connector
726         }
727 
728         /**
729          * This connector does not support connector-level, persistent locking of nodes.
730          * 
731          * @param node the node to be unlocked
732          */
733         public void unlockNode( PathNode node ) {
734             // Locking is not supported by this connector
735         }
736 
737         /**
738          * {@inheritDoc}
739          * 
740          * @see org.modeshape.graph.connector.map.MapWorkspace#query(org.modeshape.graph.ExecutionContext,
741          *      org.modeshape.graph.request.AccessQueryRequest)
742          */
743         public QueryResults query( ExecutionContext context,
744                                    AccessQueryRequest accessQuery ) {
745             throw new UnsupportedOperationException();
746         }
747 
748         /**
749          * {@inheritDoc}
750          * 
751          * @see org.modeshape.graph.connector.map.MapWorkspace#search(org.modeshape.graph.ExecutionContext, java.lang.String)
752          */
753         public QueryResults search( ExecutionContext context,
754                                     String fullTextSearchExpression ) {
755             return null;
756         }
757     }
758 
759     @SuppressWarnings( "synthetic-access" )
760     private class RootNode implements PathNode {
761         private final List<Segment> catalogNames;
762 
763         private RootNode( List<Segment> catalogNames ) {
764             this.catalogNames = catalogNames;
765         }
766 
767         public List<Segment> getChildSegments() {
768             return catalogNames;
769         }
770 
771         public Path getPath() {
772             ExecutionContext context = source.getRepositoryContext().getExecutionContext();
773             return context.getValueFactories().getPathFactory().createRootPath();
774         }
775 
776         public UUID getUuid() {
777             return source.getRootNodeUuid();
778         }
779 
780         public Map<Name, Property> getProperties() {
781             return rootNodeProperties;
782         }
783 
784         public Property getProperty( ExecutionContext context,
785                                      String name ) {
786             NameFactory nameFactory = context.getValueFactories().getNameFactory();
787             return rootNodeProperties.get(nameFactory.create(name));
788         }
789 
790         public Property getProperty( Name name ) {
791             return rootNodeProperties.get(name);
792         }
793 
794         public Set<Name> getUniqueChildNames() {
795             Set<Name> childNames = new HashSet<Name>(catalogNames.size());
796 
797             for (Segment catalogName : catalogNames) {
798                 childNames.add(catalogName.getName());
799             }
800             return childNames;
801         }
802     }
803 
804 }