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.graph.query.validate;
25  
26  import java.util.HashMap;
27  import java.util.Map;
28  import org.modeshape.common.collection.Problems;
29  import org.modeshape.common.i18n.I18n;
30  import org.modeshape.graph.GraphI18n;
31  import org.modeshape.graph.query.QueryContext;
32  import org.modeshape.graph.query.model.AllNodes;
33  import org.modeshape.graph.query.model.ArithmeticOperand;
34  import org.modeshape.graph.query.model.ChildNode;
35  import org.modeshape.graph.query.model.ChildNodeJoinCondition;
36  import org.modeshape.graph.query.model.Column;
37  import org.modeshape.graph.query.model.DescendantNode;
38  import org.modeshape.graph.query.model.DescendantNodeJoinCondition;
39  import org.modeshape.graph.query.model.DynamicOperand;
40  import org.modeshape.graph.query.model.EquiJoinCondition;
41  import org.modeshape.graph.query.model.FullTextSearch;
42  import org.modeshape.graph.query.model.FullTextSearchScore;
43  import org.modeshape.graph.query.model.Length;
44  import org.modeshape.graph.query.model.LowerCase;
45  import org.modeshape.graph.query.model.NamedSelector;
46  import org.modeshape.graph.query.model.NodeDepth;
47  import org.modeshape.graph.query.model.NodeLocalName;
48  import org.modeshape.graph.query.model.NodeName;
49  import org.modeshape.graph.query.model.NodePath;
50  import org.modeshape.graph.query.model.PropertyExistence;
51  import org.modeshape.graph.query.model.PropertyValue;
52  import org.modeshape.graph.query.model.Query;
53  import org.modeshape.graph.query.model.ReferenceValue;
54  import org.modeshape.graph.query.model.SameNode;
55  import org.modeshape.graph.query.model.SameNodeJoinCondition;
56  import org.modeshape.graph.query.model.SelectorName;
57  import org.modeshape.graph.query.model.TypeSystem;
58  import org.modeshape.graph.query.model.Visitor;
59  import org.modeshape.graph.query.model.Visitors.AbstractVisitor;
60  import org.modeshape.graph.query.validate.Schemata.Table;
61  
62  /**
63   * A {@link Visitor} implementation that validates a query's used of a {@link Schemata} and records any problems as errors.
64   */
65  public class Validator extends AbstractVisitor {
66  
67      private final QueryContext context;
68      private final Problems problems;
69      private final Map<SelectorName, Table> selectorsByNameOrAlias;
70      private final Map<SelectorName, Table> selectorsByName;
71      private final Map<String, Schemata.Column> columnsByAlias;
72      private final boolean validateColumnExistence;
73  
74      /**
75       * @param context the query context
76       * @param selectorsByName the {@link Table tables} by their name or alias, as defined by the selectors
77       */
78      public Validator( QueryContext context,
79                        Map<SelectorName, Table> selectorsByName ) {
80          this.context = context;
81          this.problems = this.context.getProblems();
82          this.selectorsByNameOrAlias = selectorsByName;
83          this.selectorsByName = new HashMap<SelectorName, Table>();
84          for (Table table : selectorsByName.values()) {
85              this.selectorsByName.put(table.getName(), table);
86          }
87          this.columnsByAlias = new HashMap<String, Schemata.Column>();
88          this.validateColumnExistence = context.getHints().validateColumnExistance;
89      }
90  
91      /**
92       * {@inheritDoc}
93       * 
94       * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.AllNodes)
95       */
96      @Override
97      public void visit( AllNodes obj ) {
98          // this table doesn't have to be in the list of selected tables
99          verifyTable(obj.name());
100     }
101 
102     /**
103      * {@inheritDoc}
104      * 
105      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.ArithmeticOperand)
106      */
107     @Override
108     public void visit( ArithmeticOperand obj ) {
109         verifyArithmeticOperand(obj.left());
110         verifyArithmeticOperand(obj.right());
111     }
112 
113     protected void verifyArithmeticOperand( DynamicOperand operand ) {
114         // The left and right operands must have LONG or DOUBLE types ...
115         if (operand instanceof NodeDepth) {
116             // good to go
117         } else if (operand instanceof Length) {
118             // good to go
119         } else if (operand instanceof ArithmeticOperand) {
120             // good to go
121         } else if (operand instanceof FullTextSearchScore) {
122             // good to go
123         } else if (operand instanceof PropertyValue) {
124             PropertyValue value = (PropertyValue)operand;
125             SelectorName selector = value.selectorName();
126             String propertyName = value.propertyName();
127             Schemata.Column column = verify(selector, propertyName, this.validateColumnExistence);
128             if (column != null) {
129                 // Check the type ...
130                 String columnType = column.getPropertyType();
131                 TypeSystem types = context.getTypeSystem();
132                 String longType = types.getLongFactory().getTypeName();
133                 String doubleType = types.getDoubleFactory().getTypeName();
134                 if (longType.equals(types.getCompatibleType(columnType, longType))) {
135                     // Then the column type is long or can be converted to long ...
136                 } else if (doubleType.equals(types.getCompatibleType(columnType, doubleType))) {
137                     // Then the column type is double or can be converted to double ...
138                 } else {
139                     I18n msg = GraphI18n.columnTypeCannotBeUsedInArithmeticOperation;
140                     problems.addError(msg, selector, propertyName, columnType);
141                 }
142             }
143         } else {
144             I18n msg = GraphI18n.dynamicOperandCannotBeUsedInArithmeticOperation;
145             problems.addError(msg, operand);
146         }
147     }
148 
149     /**
150      * {@inheritDoc}
151      * 
152      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.ChildNode)
153      */
154     @Override
155     public void visit( ChildNode obj ) {
156         verify(obj.selectorName());
157     }
158 
159     /**
160      * {@inheritDoc}
161      * 
162      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.ChildNodeJoinCondition)
163      */
164     @Override
165     public void visit( ChildNodeJoinCondition obj ) {
166         verify(obj.parentSelectorName());
167         verify(obj.childSelectorName());
168     }
169 
170     /**
171      * {@inheritDoc}
172      * 
173      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.Column)
174      */
175     @Override
176     public void visit( Column obj ) {
177         verify(obj.selectorName(), obj.propertyName(), this.validateColumnExistence); // don't care about the alias
178     }
179 
180     /**
181      * {@inheritDoc}
182      * 
183      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.DescendantNode)
184      */
185     @Override
186     public void visit( DescendantNode obj ) {
187         verify(obj.selectorName());
188     }
189 
190     /**
191      * {@inheritDoc}
192      * 
193      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.DescendantNodeJoinCondition)
194      */
195     @Override
196     public void visit( DescendantNodeJoinCondition obj ) {
197         verify(obj.ancestorSelectorName());
198         verify(obj.descendantSelectorName());
199     }
200 
201     /**
202      * {@inheritDoc}
203      * 
204      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.EquiJoinCondition)
205      */
206     @Override
207     public void visit( EquiJoinCondition obj ) {
208         verify(obj.selector1Name(), obj.property1Name(), this.validateColumnExistence);
209         verify(obj.selector2Name(), obj.property2Name(), this.validateColumnExistence);
210     }
211 
212     /**
213      * {@inheritDoc}
214      * 
215      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.FullTextSearch)
216      */
217     @Override
218     public void visit( FullTextSearch obj ) {
219         SelectorName selectorName = obj.selectorName();
220         if (obj.propertyName() != null) {
221             Schemata.Column column = verify(selectorName, obj.propertyName(), this.validateColumnExistence);
222             if (column != null) {
223                 // Make sure the column is full-text searchable ...
224                 if (!column.isFullTextSearchable()) {
225                     problems.addError(GraphI18n.columnIsNotFullTextSearchable, column.getName(), selectorName);
226                 }
227             }
228         } else {
229             Table table = verify(selectorName);
230             // Don't need to check if the selector is the '__ALLNODES__' selector ...
231             if (table != null && !AllNodes.ALL_NODES_NAME.equals(table.getName())) {
232                 // Make sure there is at least one column on the table that is full-text searchable ...
233                 boolean searchable = false;
234                 for (Schemata.Column column : table.getColumns()) {
235                     if (column.isFullTextSearchable()) {
236                         searchable = true;
237                         break;
238                     }
239                 }
240                 if (!searchable) {
241                     problems.addError(GraphI18n.tableIsNotFullTextSearchable, selectorName);
242                 }
243             }
244         }
245     }
246 
247     /**
248      * {@inheritDoc}
249      * 
250      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.FullTextSearchScore)
251      */
252     @Override
253     public void visit( FullTextSearchScore obj ) {
254         verify(obj.selectorName());
255     }
256 
257     /**
258      * {@inheritDoc}
259      * 
260      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.Length)
261      */
262     @Override
263     public void visit( Length obj ) {
264         verify(obj.selectorName());
265     }
266 
267     /**
268      * {@inheritDoc}
269      * 
270      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.LowerCase)
271      */
272     @Override
273     public void visit( LowerCase obj ) {
274         verify(obj.selectorName());
275     }
276 
277     /**
278      * {@inheritDoc}
279      * 
280      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.NamedSelector)
281      */
282     @Override
283     public void visit( NamedSelector obj ) {
284         verify(obj.aliasOrName());
285     }
286 
287     /**
288      * {@inheritDoc}
289      * 
290      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.NodeDepth)
291      */
292     @Override
293     public void visit( NodeDepth obj ) {
294         verify(obj.selectorName());
295     }
296 
297     /**
298      * {@inheritDoc}
299      * 
300      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.NodeLocalName)
301      */
302     @Override
303     public void visit( NodeLocalName obj ) {
304         verify(obj.selectorName());
305     }
306 
307     /**
308      * {@inheritDoc}
309      * 
310      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.NodeName)
311      */
312     @Override
313     public void visit( NodeName obj ) {
314         verify(obj.selectorName());
315     }
316 
317     /**
318      * {@inheritDoc}
319      * 
320      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.NodePath)
321      */
322     @Override
323     public void visit( NodePath obj ) {
324         verify(obj.selectorName());
325     }
326 
327     /**
328      * {@inheritDoc}
329      * 
330      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.PropertyExistence)
331      */
332     @Override
333     public void visit( PropertyExistence obj ) {
334         verify(obj.selectorName(), obj.propertyName(), this.validateColumnExistence);
335     }
336 
337     /**
338      * {@inheritDoc}
339      * 
340      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.PropertyValue)
341      */
342     @Override
343     public void visit( PropertyValue obj ) {
344         verify(obj.selectorName(), obj.propertyName(), this.validateColumnExistence);
345     }
346 
347     /**
348      * {@inheritDoc}
349      * 
350      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.ReferenceValue)
351      */
352     @Override
353     public void visit( ReferenceValue obj ) {
354         String propName = obj.propertyName();
355         if (propName != null) {
356             verify(obj.selectorName(), propName, this.validateColumnExistence);
357         } else {
358             verify(obj.selectorName());
359         }
360     }
361 
362     /**
363      * {@inheritDoc}
364      * 
365      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.Query)
366      */
367     @Override
368     public void visit( Query obj ) {
369         // Collect the map of columns by alias for this query ...
370         this.columnsByAlias.clear();
371         for (Column column : obj.columns()) {
372             // Find the schemata column ...
373             Table table = tableWithNameOrAlias(column.selectorName());
374             if (table != null) {
375                 Schemata.Column tableColumn = table.getColumn(column.propertyName());
376                 if (tableColumn != null) {
377                     this.columnsByAlias.put(column.columnName(), tableColumn);
378                 }
379             }
380         }
381         super.visit(obj);
382     }
383 
384     /**
385      * {@inheritDoc}
386      * 
387      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.SameNode)
388      */
389     @Override
390     public void visit( SameNode obj ) {
391         verify(obj.selectorName());
392     }
393 
394     /**
395      * {@inheritDoc}
396      * 
397      * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.SameNodeJoinCondition)
398      */
399     @Override
400     public void visit( SameNodeJoinCondition obj ) {
401         verify(obj.selector1Name());
402         verify(obj.selector2Name());
403     }
404 
405     protected Table tableWithNameOrAlias( SelectorName tableName ) {
406         Table table = selectorsByNameOrAlias.get(tableName);
407         if (table == null) {
408             // Try looking up the table by it's real name (if an alias were used) ...
409             table = selectorsByName.get(tableName);
410         }
411         return table;
412     }
413 
414     protected Table verify( SelectorName selectorName ) {
415         Table table = tableWithNameOrAlias(selectorName);
416         if (table == null) {
417             problems.addError(GraphI18n.tableDoesNotExist, selectorName.name());
418         }
419         return table;
420     }
421 
422     protected Table verifyTable( SelectorName tableName ) {
423         Table table = tableWithNameOrAlias(tableName);
424         if (table == null) {
425             problems.addError(GraphI18n.tableDoesNotExist, tableName.name());
426         }
427         return table;
428     }
429 
430     protected Schemata.Column verify( SelectorName selectorName,
431                                       String propertyName,
432                                       boolean columnIsRequired ) {
433         Table table = tableWithNameOrAlias(selectorName);
434         if (table == null) {
435             problems.addError(GraphI18n.tableDoesNotExist, selectorName.name());
436             return null;
437         }
438         Schemata.Column column = table.getColumn(propertyName);
439         if (column == null) {
440             // Maybe the supplied property name is really an alias ...
441             column = this.columnsByAlias.get(propertyName);
442             if (column == null && columnIsRequired) {
443                 problems.addError(GraphI18n.columnDoesNotExistOnTable, propertyName, selectorName.name());
444             }
445         }
446         return column; // may be null
447     }
448 
449 }