1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
63
64
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
94 for (Namespace namespace : context.getNamespaceRegistry().getNamespaces()) {
95 this.prefixesByUris.put(namespace.getNamespaceUri(), namespace.getPrefix());
96 }
97
98
99 for (JcrNodeType nodeType : nodeTypesByName.values()) {
100
101 for (JcrNodeType supertype : nodeType.getTypeAndSupertypes()) {
102 subtypesByName.put(supertype, nodeType);
103 }
104 }
105
106
107 TypeSystem typeSystem = context.getValueFactories().getTypeSystem();
108 ImmutableSchemata.Builder builder = ImmutableSchemata.createBuilder(typeSystem);
109
110
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
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
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
168
169
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
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
221 type = typeSystem.getCompatibleType(previousType, type);
222 }
223 boolean fullTextSearchable = fullTextSearchableNames.contains(columnName) || defn.isFullTextSearchable();
224 if (fullTextSearchable) fullTextSearchableNames.add(columnName);
225
226 boolean orderable = defn.isQueryOrderable();
227 Set<Operator> operators = operatorsFor(defn);
228 builder.addColumn(tableName, columnName, type, fullTextSearchable, orderable, operators);
229
230
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
252
253 type = typeSystem.getCompatibleType(previousType, type);
254 }
255
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
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
288
289
290
291
292
293
294
295
296
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
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
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
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
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
387 return;
388 }
389 viewDefinition.append(" FROM ").append(AllNodes.ALL_NODES_NAME).append(" AS [").append(tableName).append(']');
390
391
392 if (!JcrNtLexicon.BASE.equals(nodeType.getInternalName())) {
393
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
436 builder.addView(tableName, viewDefinition.toString());
437
438 if (hasResidualProperties) {
439
440 builder.markExtraColumns(tableName);
441 }
442 }
443
444
445
446
447
448
449 public Table getTable( SelectorName name ) {
450 return schemata.getTable(name);
451 }
452
453
454
455
456
457
458
459
460
461 public Schemata getSchemataForSession( JcrSession session ) {
462 assert session != null;
463
464 if (!overridesNamespaceMappings(session)) {
465
466 return this;
467 }
468
469
470 return new SessionSchemata(session);
471 }
472
473
474
475
476
477
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
485 return false;
486 }
487 for (Namespace namespace : localNamespaces) {
488 if (prefixesByUris.containsKey(namespace.getNamespaceUri())) return true;
489 }
490
491 return false;
492 }
493
494 for (Namespace namespace : registry.getNamespaces()) {
495 String expectedPrefix = prefixesByUris.get(namespace.getNamespaceUri());
496 if (expectedPrefix == null) {
497
498 continue;
499 }
500 if (!namespace.getPrefix().equals(expectedPrefix)) return true;
501 }
502 return false;
503 }
504
505
506
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
522 addAllNodesTable(builder, null, context, null);
523 this.schemata = builder.build();
524 }
525
526
527
528
529
530
531 public Table getTable( SelectorName name ) {
532 Table table = schemata.getTable(name);
533 if (table == null) {
534
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 }