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.jcr;
25  
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.EnumSet;
29  import java.util.HashMap;
30  import java.util.HashSet;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  import javax.jcr.PropertyType;
35  import javax.jcr.Value;
36  import javax.jcr.nodetype.NodeType;
37  import javax.jcr.nodetype.PropertyDefinition;
38  import javax.jcr.version.OnParentVersionAction;
39  import net.jcip.annotations.Immutable;
40  import net.jcip.annotations.NotThreadSafe;
41  import org.apache.lucene.document.Field;
42  import org.apache.lucene.document.Field.Index;
43  import org.apache.lucene.document.Field.Store;
44  import org.modeshape.graph.ExecutionContext;
45  import org.modeshape.graph.property.Name;
46  import org.modeshape.graph.property.NameFactory;
47  import org.modeshape.graph.property.NamespaceRegistry;
48  import org.modeshape.graph.property.NamespaceRegistry.Namespace;
49  import org.modeshape.graph.property.basic.LocalNamespaceRegistry;
50  import org.modeshape.graph.query.model.AllNodes;
51  import org.modeshape.graph.query.model.Operator;
52  import org.modeshape.graph.query.model.SelectorName;
53  import org.modeshape.graph.query.model.TypeSystem;
54  import org.modeshape.graph.query.validate.ImmutableSchemata;
55  import org.modeshape.graph.query.validate.Schemata;
56  import org.modeshape.search.lucene.IndexRules;
57  import org.modeshape.search.lucene.LuceneSearchEngine;
58  import com.google.common.collect.LinkedHashMultimap;
59  import com.google.common.collect.Multimap;
60  
61  /**
62   * A {@link Schemata} implementation that is constructed from the {@link NodeType}s and {@link PropertyDefinition}s contained
63   * within a {@link RepositoryNodeTypeManager}. The resulting {@link Schemata.Table}s will never change, so the
64   * {@link RepositoryNodeTypeManager} must replace it's cached instance whenever the node types change.
65   */
66  @Immutable
67  class NodeTypeSchemata implements Schemata {
68  
69      protected static final boolean DEFAULT_CAN_CONTAIN_REFERENCES = true;
70      protected static final boolean DEFAULT_FULL_TEXT_SEARCHABLE = true;
71  
72      private final Schemata schemata;
73      private final Map<Integer, String> types;
74      private final Map<String, String> prefixesByUris = new HashMap<String, String>();
75      private final boolean includeColumnsForInheritedProperties;
76      private final boolean includePseudoColumnsInSelectStar;
77      private final Collection<JcrPropertyDefinition> propertyDefinitions;
78      private final Map<Name, JcrNodeType> nodeTypesByName;
79      private final Multimap<JcrNodeType, JcrNodeType> subtypesByName = LinkedHashMultimap.create();
80      private final IndexRules indexRules;
81      private final List<JcrPropertyDefinition> pseudoProperties = new ArrayList<JcrPropertyDefinition>();
82  
83      NodeTypeSchemata( ExecutionContext context,
84                        Map<Name, JcrNodeType> nodeTypes,
85                        Collection<JcrPropertyDefinition> propertyDefinitions,
86                        boolean includeColumnsForInheritedProperties,
87                        boolean includePseudoColumnsInSelectStar ) {
88          this.includeColumnsForInheritedProperties = includeColumnsForInheritedProperties;
89          this.includePseudoColumnsInSelectStar = includePseudoColumnsInSelectStar;
90          this.propertyDefinitions = propertyDefinitions;
91          this.nodeTypesByName = nodeTypes;
92  
93          // Register all the namespace prefixes by URIs ...
94          for (Namespace namespace : context.getNamespaceRegistry().getNamespaces()) {
95              this.prefixesByUris.put(namespace.getNamespaceUri(), namespace.getPrefix());
96          }
97  
98          // Identify the subtypes for each node type, and do this before we build any views ...
99          for (JcrNodeType nodeType : nodeTypesByName.values()) {
100             // For each of the supertypes ...
101             for (JcrNodeType supertype : nodeType.getTypeAndSupertypes()) {
102                 subtypesByName.put(supertype, nodeType);
103             }
104         }
105 
106         // Build the schemata for the current node types ...
107         TypeSystem typeSystem = context.getValueFactories().getTypeSystem();
108         ImmutableSchemata.Builder builder = ImmutableSchemata.createBuilder(typeSystem);
109 
110         // Build the fast-search for type names based upon PropertyType values ...
111         types = new HashMap<Integer, String>();
112         types.put(PropertyType.BINARY, typeSystem.getBinaryFactory().getTypeName());
113         types.put(PropertyType.BOOLEAN, typeSystem.getBooleanFactory().getTypeName());
114         types.put(PropertyType.DATE, typeSystem.getDateTimeFactory().getTypeName());
115         types.put(PropertyType.DECIMAL, typeSystem.getDecimalFactory().getTypeName());
116         types.put(PropertyType.DOUBLE, typeSystem.getDoubleFactory().getTypeName());
117         types.put(PropertyType.LONG, typeSystem.getLongFactory().getTypeName());
118         types.put(PropertyType.PATH, typeSystem.getPathFactory().getTypeName());
119         types.put(PropertyType.REFERENCE, typeSystem.getReferenceFactory().getTypeName());
120         types.put(PropertyType.WEAKREFERENCE, typeSystem.getReferenceFactory().getTypeName());
121         types.put(PropertyType.STRING, typeSystem.getStringFactory().getTypeName());
122         types.put(PropertyType.NAME, typeSystem.getStringFactory().getTypeName());
123         types.put(PropertyType.URI, typeSystem.getStringFactory().getTypeName());
124 
125         pseudoProperties.add(pseudoProperty(context, JcrLexicon.PATH, PropertyType.PATH));
126         pseudoProperties.add(pseudoProperty(context, JcrLexicon.NAME, PropertyType.NAME));
127         pseudoProperties.add(pseudoProperty(context, JcrLexicon.SCORE, PropertyType.DOUBLE));
128         pseudoProperties.add(pseudoProperty(context, ModeShapeLexicon.LOCALNAME, PropertyType.STRING));
129         pseudoProperties.add(pseudoProperty(context, ModeShapeLexicon.DEPTH, PropertyType.LONG));
130 
131         // Create the "ALLNODES" table, which will contain all possible properties ...
132         IndexRules.Builder indexRulesBuilder = IndexRules.createBuilder(LuceneSearchEngine.DEFAULT_RULES);
133         indexRulesBuilder.defaultTo(Field.Store.YES,
134                                     Field.Index.ANALYZED,
135                                     DEFAULT_CAN_CONTAIN_REFERENCES,
136                                     DEFAULT_FULL_TEXT_SEARCHABLE);
137         addAllNodesTable(builder, indexRulesBuilder, context, pseudoProperties);
138 
139         // Define a view for each node type ...
140         for (JcrNodeType nodeType : nodeTypesByName.values()) {
141             addView(builder, context, nodeType);
142         }
143 
144         schemata = builder.build();
145         indexRules = indexRulesBuilder.build();
146     }
147 
148     protected JcrPropertyDefinition pseudoProperty( ExecutionContext context,
149                                                     Name name,
150                                                     int propertyType ) {
151         int opv = OnParentVersionAction.IGNORE;
152         boolean autoCreated = true;
153         boolean mandatory = true;
154         boolean isProtected = true;
155         boolean multiple = false;
156         boolean fullTextSearchable = false;
157         boolean queryOrderable = true;
158         Value[] defaultValues = null;
159         String[] valueConstraints = new String[] {};
160         String[] queryOperators = null;
161         return new JcrPropertyDefinition(context, null, name, opv, autoCreated, mandatory, isProtected, defaultValues,
162                                          propertyType, valueConstraints, multiple, fullTextSearchable, queryOrderable,
163                                          queryOperators);
164     }
165 
166     /**
167      * Get the index rules ...
168      * 
169      * @return indexRules
170      */
171     public IndexRules getIndexRules() {
172         return indexRules;
173     }
174 
175     protected JcrNodeType getNodeType( Name nodeTypeName ) {
176         return nodeTypesByName.get(nodeTypeName);
177     }
178 
179     protected final void addAllNodesTable( ImmutableSchemata.Builder builder,
180                                            IndexRules.Builder indexRuleBuilder,
181                                            ExecutionContext context,
182                                            List<JcrPropertyDefinition> additionalProperties ) {
183         NamespaceRegistry registry = context.getNamespaceRegistry();
184         TypeSystem typeSystem = context.getValueFactories().getTypeSystem();
185 
186         String tableName = AllNodes.ALL_NODES_NAME.name();
187         boolean first = true;
188         Map<String, String> typesForNames = new HashMap<String, String>();
189         Set<String> fullTextSearchableNames = new HashSet<String>();
190         for (JcrPropertyDefinition defn : propertyDefinitions) {
191             if (defn.isResidual()) continue;
192             if (defn.isPrivate()) continue;
193             // if (defn.isMultiple()) continue;
194             Name name = defn.getInternalName();
195 
196             String columnName = name.getString(registry);
197             if (first) {
198                 builder.addTable(tableName, columnName);
199                 first = false;
200             }
201             boolean canBeReference = false;
202             boolean isStrongReference = false;
203             switch (defn.getRequiredType()) {
204                 case PropertyType.REFERENCE:
205                     isStrongReference = true;
206                     canBeReference = true;
207                     break;
208                 case PropertyType.WEAKREFERENCE:
209                 case PropertyType.UNDEFINED:
210                     canBeReference = true;
211                     break;
212             }
213             String type = typeSystem.getDefaultType();
214             if (defn.getRequiredType() != PropertyType.UNDEFINED) {
215                 type = types.get(defn.getRequiredType());
216             }
217             assert type != null;
218             String previousType = typesForNames.put(columnName, type);
219             if (previousType != null && !previousType.equals(type)) {
220                 // There are two property definitions with the same name but different types, so we need to find a common type ...
221                 type = typeSystem.getCompatibleType(previousType, type);
222             }
223             boolean fullTextSearchable = fullTextSearchableNames.contains(columnName) || defn.isFullTextSearchable();
224             if (fullTextSearchable) fullTextSearchableNames.add(columnName);
225             // Add (or overwrite) the column ...
226             boolean orderable = defn.isQueryOrderable();
227             Set<Operator> operators = operatorsFor(defn);
228             builder.addColumn(tableName, columnName, type, fullTextSearchable, orderable, operators);
229 
230             // And build an indexing rule for this type ...
231             if (indexRuleBuilder != null) addIndexRule(indexRuleBuilder,
232                                                        defn,
233                                                        type,
234                                                        typeSystem,
235                                                        canBeReference,
236                                                        isStrongReference);
237         }
238         if (additionalProperties != null) {
239             boolean canBeReference = false;
240             boolean isStrongReference = false;
241             boolean fullTextSearchable = false;
242             assert !first;
243             for (JcrPropertyDefinition defn : additionalProperties) {
244                 Name name = defn.getInternalName();
245                 String columnName = name.getString(registry);
246                 assert defn.getRequiredType() != PropertyType.UNDEFINED;
247                 String type = types.get(defn.getRequiredType());
248                 assert type != null;
249                 String previousType = typesForNames.put(columnName, type);
250                 if (previousType != null && !previousType.equals(type)) {
251                     // There are two property definitions with the same name but different types, so we need to find a common type
252                     // ...
253                     type = typeSystem.getCompatibleType(previousType, type);
254                 }
255                 // Add (or overwrite) the column ...
256                 boolean orderable = defn.isQueryOrderable();
257                 Set<Operator> operators = operatorsFor(defn);
258                 builder.addColumn(tableName, columnName, type, fullTextSearchable, orderable, operators);
259                 if (!includePseudoColumnsInSelectStar) {
260                     builder.excludeFromSelectStar(tableName, columnName);
261                 }
262 
263                 // And build an indexing rule for this type ...
264                 if (indexRuleBuilder != null) addIndexRule(indexRuleBuilder,
265                                                            defn,
266                                                            type,
267                                                            typeSystem,
268                                                            canBeReference,
269                                                            isStrongReference);
270             }
271         }
272     }
273 
274     protected Set<Operator> operatorsFor( JcrPropertyDefinition defn ) {
275         String[] ops = defn.getAvailableQueryOperators();
276         if (ops == null || ops.length == 0) return EnumSet.allOf(Operator.class);
277         Set<Operator> result = new HashSet<Operator>();
278         for (String symbol : ops) {
279             Operator op = JcrPropertyDefinition.operatorFromSymbol(symbol);
280             assert op != null;
281             result.add(op);
282         }
283         return result;
284     }
285 
286     /**
287      * Add an index rule for the given property definition and the type in the {@link TypeSystem}.
288      * 
289      * @param builder the index rule builder; never null
290      * @param defn the property definition; never null
291      * @param type the TypeSystem type, which may be a more general type than dictated by the definition, since multiple
292      *        definitions with the same name require the index rule to use the common base type; never null
293      * @param typeSystem the type system; never null
294      * @param canBeReference true if the property described the rule can hold reference values, or false otherwise
295      * @param isStrongReference true if the index rule can be a reference and it should be included in referential integrity
296      *        checks
297      */
298     protected final void addIndexRule( IndexRules.Builder builder,
299                                        JcrPropertyDefinition defn,
300                                        String type,
301                                        TypeSystem typeSystem,
302                                        boolean canBeReference,
303                                        boolean isStrongReference ) {
304         Store store = Store.YES;
305         Index index = defn.isFullTextSearchable() ? Index.ANALYZED : Index.NO;
306         if (typeSystem.getStringFactory().getTypeName().equals(type)) {
307             builder.stringField(defn.getInternalName(), store, index, canBeReference, defn.isFullTextSearchable());
308         } else if (typeSystem.getDateTimeFactory().getTypeName().equals(type)) {
309             Long minimum = typeSystem.getLongFactory().create(defn.getMinimumValue());
310             Long maximum = typeSystem.getLongFactory().create(defn.getMaximumValue());
311             builder.dateField(defn.getInternalName(), store, index, minimum, maximum);
312         } else if (typeSystem.getLongFactory().getTypeName().equals(type)) {
313             Long minimum = typeSystem.getLongFactory().create(defn.getMinimumValue());
314             Long maximum = typeSystem.getLongFactory().create(defn.getMaximumValue());
315             builder.longField(defn.getInternalName(), store, index, minimum, maximum);
316         } else if (typeSystem.getDoubleFactory().getTypeName().equals(type)) {
317             Double minimum = typeSystem.getDoubleFactory().create(defn.getMinimumValue());
318             Double maximum = typeSystem.getDoubleFactory().create(defn.getMaximumValue());
319             builder.doubleField(defn.getInternalName(), store, index, minimum, maximum);
320         } else if (typeSystem.getBooleanFactory().getTypeName().equals(type)) {
321             builder.booleanField(defn.getInternalName(), store, index);
322         } else if (typeSystem.getBinaryFactory().getTypeName().equals(type)) {
323             store = Store.NO;
324             builder.binaryField(defn.getInternalName(), store, index, defn.isFullTextSearchable());
325         } else if (typeSystem.getReferenceFactory().getTypeName().equals(type)) {
326             store = Store.NO;
327             builder.referenceField(defn.getInternalName(), store, index);
328         } else if (typeSystem.getPathFactory().getTypeName().equals(type)) {
329             builder.pathField(defn.getInternalName(), store, index);
330         } else {
331             // Everything else gets stored as a string ...
332             builder.stringField(defn.getInternalName(), store, index, canBeReference, defn.isFullTextSearchable());
333         }
334 
335     }
336 
337     protected final void addView( ImmutableSchemata.Builder builder,
338                                   ExecutionContext context,
339                                   JcrNodeType nodeType ) {
340         NamespaceRegistry registry = context.getNamespaceRegistry();
341 
342         if (!nodeType.isQueryable()) {
343             // The node type is defined as not queryable, so skip it ...
344             return;
345         }
346 
347         String tableName = nodeType.getName();
348         JcrPropertyDefinition[] defns = null;
349         if (includeColumnsForInheritedProperties) {
350             defns = nodeType.getPropertyDefinitions();
351         } else {
352             defns = nodeType.getDeclaredPropertyDefinitions();
353         }
354         // Create the SQL statement ...
355         StringBuilder viewDefinition = new StringBuilder("SELECT ");
356         boolean hasResidualProperties = false;
357         boolean first = true;
358         for (JcrPropertyDefinition defn : defns) {
359             if (defn.isResidual()) {
360                 hasResidualProperties = true;
361                 continue;
362             }
363             if (defn.isMultiple()) continue;
364             if (defn.isPrivate()) continue;
365             Name name = defn.getInternalName();
366 
367             String columnName = name.getString(registry);
368             if (first) first = false;
369             else viewDefinition.append(',');
370             viewDefinition.append('[').append(columnName).append(']');
371             if (!defn.isQueryOrderable()) {
372                 builder.markOrderable(tableName, columnName, false);
373             }
374             builder.markOperators(tableName, columnName, operatorsFor(defn));
375         }
376         // Add the pseudo-properties ...
377         for (JcrPropertyDefinition defn : pseudoProperties) {
378             Name name = defn.getInternalName();
379             String columnName = name.getString(registry);
380             if (first) first = false;
381             else viewDefinition.append(',');
382             viewDefinition.append('[').append(columnName).append(']');
383             builder.markOperators(tableName, columnName, operatorsFor(defn));
384         }
385         if (first) {
386             // All the properties were skipped ...
387             return;
388         }
389         viewDefinition.append(" FROM ").append(AllNodes.ALL_NODES_NAME).append(" AS [").append(tableName).append(']');
390 
391         // The 'nt:base' node type will have every single object in it, so we don't need to add the type criteria ...
392         if (!JcrNtLexicon.BASE.equals(nodeType.getInternalName())) {
393             // The node type is not 'nt:base', which
394             viewDefinition.append(" WHERE ");
395 
396             int mixinTypeCount = 0;
397             int primaryTypeCount = 0;
398             StringBuilder mixinTypes = new StringBuilder();
399             StringBuilder primaryTypes = new StringBuilder();
400             Collection<JcrNodeType> typeAndSubtypes = subtypesByName.get(nodeType);
401             for (JcrNodeType thisOrSupertype : typeAndSubtypes) {
402                 if (thisOrSupertype.isMixin()) {
403                     if (mixinTypeCount > 0) mixinTypes.append(',');
404                     assert prefixesByUris.containsKey(thisOrSupertype.getInternalName().getNamespaceUri());
405                     String name = thisOrSupertype.getInternalName().getString(registry);
406                     mixinTypes.append('[').append(name).append(']');
407                     ++mixinTypeCount;
408                 } else {
409                     if (primaryTypeCount > 0) primaryTypes.append(',');
410                     assert prefixesByUris.containsKey(thisOrSupertype.getInternalName().getNamespaceUri());
411                     String name = thisOrSupertype.getInternalName().getString(registry);
412                     primaryTypes.append('[').append(name).append(']');
413                     ++primaryTypeCount;
414                 }
415             }
416             if (primaryTypeCount > 0) {
417                 viewDefinition.append('[').append(JcrLexicon.PRIMARY_TYPE.getString(registry)).append(']');
418                 if (primaryTypeCount == 1) {
419                     viewDefinition.append('=').append(primaryTypes);
420                 } else {
421                     viewDefinition.append(" IN (").append(primaryTypes).append(')');
422                 }
423             }
424             if (mixinTypeCount > 0) {
425                 if (primaryTypeCount > 0) viewDefinition.append(" OR ");
426                 viewDefinition.append('[').append(JcrLexicon.MIXIN_TYPES.getString(registry)).append(']');
427                 if (mixinTypeCount == 1) {
428                     viewDefinition.append('=').append(mixinTypes);
429                 } else {
430                     viewDefinition.append(" IN (").append(mixinTypes).append(')');
431                 }
432             }
433         }
434 
435         // Define the view ...
436         builder.addView(tableName, viewDefinition.toString());
437 
438         if (hasResidualProperties) {
439             // Record that there are residual properties ...
440             builder.markExtraColumns(tableName);
441         }
442     }
443 
444     /**
445      * {@inheritDoc}
446      * 
447      * @see org.modeshape.graph.query.validate.Schemata#getTable(org.modeshape.graph.query.model.SelectorName)
448      */
449     public Table getTable( SelectorName name ) {
450         return schemata.getTable(name);
451     }
452 
453     /**
454      * Get a schemata instance that works with the suppplied session and that uses the session-specific namespace mappings. Note
455      * that the resulting instance does not change as the session's namespace mappings are changed, so when that happens the
456      * JcrSession must call this method again to obtain a new schemata.
457      * 
458      * @param session the session; may not be null
459      * @return the schemata that can be used for the session; never null
460      */
461     public Schemata getSchemataForSession( JcrSession session ) {
462         assert session != null;
463         // If the session does not override any namespace mappings used in this schemata ...
464         if (!overridesNamespaceMappings(session)) {
465             // Then we can just use this schemata instance ...
466             return this;
467         }
468 
469         // Otherwise, the session has some custom namespace mappings, so we need to return a session-specific instance...
470         return new SessionSchemata(session);
471     }
472 
473     /**
474      * Determine if the session overrides any namespace mappings used by this schemata.
475      * 
476      * @param session the session; may not be null
477      * @return true if the session overrides one or more namespace mappings used in this schemata, or false otherwise
478      */
479     private boolean overridesNamespaceMappings( JcrSession session ) {
480         NamespaceRegistry registry = session.getExecutionContext().getNamespaceRegistry();
481         if (registry instanceof LocalNamespaceRegistry) {
482             Set<Namespace> localNamespaces = ((LocalNamespaceRegistry)registry).getLocalNamespaces();
483             if (localNamespaces.isEmpty()) {
484                 // There are no local mappings ...
485                 return false;
486             }
487             for (Namespace namespace : localNamespaces) {
488                 if (prefixesByUris.containsKey(namespace.getNamespaceUri())) return true;
489             }
490             // None of the local namespace mappings overrode any namespaces used by this schemata ...
491             return false;
492         }
493         // We can't find the local mappings, so brute-force it ...
494         for (Namespace namespace : registry.getNamespaces()) {
495             String expectedPrefix = prefixesByUris.get(namespace.getNamespaceUri());
496             if (expectedPrefix == null) {
497                 // This namespace is not used by this schemata ...
498                 continue;
499             }
500             if (!namespace.getPrefix().equals(expectedPrefix)) return true;
501         }
502         return false;
503     }
504 
505     /**
506      * Implementation class that builds the tables lazily.
507      */
508     @NotThreadSafe
509     protected class SessionSchemata implements Schemata {
510         private final JcrSession session;
511         private final ExecutionContext context;
512         private final ImmutableSchemata.Builder builder;
513         private final NameFactory nameFactory;
514         private Schemata schemata;
515 
516         protected SessionSchemata( JcrSession session ) {
517             this.session = session;
518             this.context = this.session.getExecutionContext();
519             this.nameFactory = context.getValueFactories().getNameFactory();
520             this.builder = ImmutableSchemata.createBuilder(context.getValueFactories().getTypeSystem());
521             // Add the "AllNodes" table ...
522             addAllNodesTable(builder, null, context, null);
523             this.schemata = builder.build();
524         }
525 
526         /**
527          * {@inheritDoc}
528          * 
529          * @see org.modeshape.graph.query.validate.Schemata#getTable(org.modeshape.graph.query.model.SelectorName)
530          */
531         public Table getTable( SelectorName name ) {
532             Table table = schemata.getTable(name);
533             if (table == null) {
534                 // Try getting it ...
535                 Name nodeTypeName = nameFactory.create(name.name());
536                 JcrNodeType nodeType = getNodeType(nodeTypeName);
537                 if (nodeType == null) return null;
538                 addView(builder, context, nodeType);
539                 schemata = builder.build();
540             }
541             return schemata.getTable(name);
542         }
543     }
544 
545 }