001    package mondrian.gui.validate;
002    
003    import java.lang.reflect.Field;
004    
005    import mondrian.gui.MondrianGuiDef;
006    
007    /**
008     * Validates a <code>MondrianGuiDef</code>. Class contains <code>invalid</code>
009     * method formerly from <code>mondrian.gui.SchemaTreeCellRenderer</code>.
010     *
011     * @author mlowery
012     */
013    public class ValidationUtils {
014    
015        public static String invalid(Messages messages, JDBCValidator jdbcValidator, TreeModel treeModel,
016                TreeModelPath tpath, Object value, Object icube, Object iparentDimension, Object iparentHierarchy,
017                Object iparentLevel) {
018            //String errMsg = null;
019            String nameMustBeSet = messages.getString("schemaTreeCellRenderer.nameMustBeSet.alert", "Name must be set");
020    
021            MondrianGuiDef.Cube cube = (MondrianGuiDef.Cube) icube; //null;
022            MondrianGuiDef.Dimension parentDimension = (MondrianGuiDef.Dimension) iparentDimension; // null // used only by level to check for leveltype value
023            MondrianGuiDef.Hierarchy parentHierarchy = (MondrianGuiDef.Hierarchy) iparentHierarchy; //null; // used only by level validation
024            MondrianGuiDef.Level parentLevel = (MondrianGuiDef.Level) iparentLevel; // null // used only by property validation
025    
026            if (!tpath.isEmpty()) {
027                int pathcount = tpath.getPathCount();
028                for (int i = 0; i < pathcount
029                        && (cube == null || parentDimension == null || parentHierarchy == null || parentLevel == null); i++) {
030                    if (tpath.getPathComponent(i) instanceof MondrianGuiDef.Cube && cube == null) {
031                        cube = (MondrianGuiDef.Cube) tpath.getPathComponent(i);
032                    }
033                    if (tpath.getPathComponent(i) instanceof MondrianGuiDef.Dimension && parentDimension == null) {
034                        parentDimension = (MondrianGuiDef.Dimension) tpath.getPathComponent(i);
035                    }
036                    if (tpath.getPathComponent(i) instanceof MondrianGuiDef.Hierarchy && parentHierarchy == null) {
037                        parentHierarchy = (MondrianGuiDef.Hierarchy) tpath.getPathComponent(i);
038                    }
039                    if (tpath.getPathComponent(i) instanceof MondrianGuiDef.Level && parentLevel == null) {
040                        parentLevel = (MondrianGuiDef.Level) tpath.getPathComponent(i);
041                    }
042                }
043            }
044    
045            //Step 1: check validity of this value object
046            if (value instanceof MondrianGuiDef.Schema) {
047                if (isEmpty(((MondrianGuiDef.Schema) value).name)) {
048                    return nameMustBeSet;
049                }
050            } else if (value instanceof MondrianGuiDef.VirtualCube) {
051                if (isEmpty(((MondrianGuiDef.VirtualCube) value).name)) {
052                    return nameMustBeSet;
053                }
054            } else if (value instanceof MondrianGuiDef.VirtualCubeDimension) {
055                if (isEmpty(((MondrianGuiDef.VirtualCubeDimension) value).name)) {
056                    return nameMustBeSet;
057                }
058            } else if (value instanceof MondrianGuiDef.VirtualCubeMeasure) {
059                if (isEmpty(((MondrianGuiDef.VirtualCubeMeasure) value).name)) {
060                    return nameMustBeSet;
061                }
062            } else if (value instanceof MondrianGuiDef.Cube) {
063                if (isEmpty(((MondrianGuiDef.Cube) value).name)) {
064                    return nameMustBeSet;
065                }
066                if (((MondrianGuiDef.Cube) value).fact == null
067                        || isEmpty(((MondrianGuiDef.Table) ((MondrianGuiDef.Cube) value).fact).name)) //check name is not blank
068                {
069                    return messages.getString("schemaTreeCellRenderer.factNameMustBeSet.alert", "Fact name must be set");
070                }
071    
072                // database validity check, if database connection is successful
073                if (jdbcValidator.isInitialized()) {
074    
075                    //Vector allTables            = jdbcMetaData.getAllTables(((MondrianGuiDef.Table) ((MondrianGuiDef.Cube) value).fact).schema);
076                    String schemaName = ((MondrianGuiDef.Table) ((MondrianGuiDef.Cube) value).fact).schema;
077                    String factTable = ((MondrianGuiDef.Table) ((MondrianGuiDef.Cube) value).fact).name;
078                    if (!jdbcValidator.isTableExists(schemaName, factTable)) {
079                        return messages.getFormattedString("schemaTreeCellRenderer.factTableDoesNotExist.alert",
080                                "Fact table {0} does not exist in database {1}", new String[] { factTable,
081                                        ((schemaName == null || schemaName.equals("")) ? "." : "schema " + schemaName) });
082                    }
083                }
084            } else if (value instanceof MondrianGuiDef.CubeDimension) {
085                if (isEmpty(((MondrianGuiDef.CubeDimension) value).name)) //check name is not blank
086                {
087                    return nameMustBeSet;
088                }
089                if (value instanceof MondrianGuiDef.DimensionUsage) {
090                    if (isEmpty(((MondrianGuiDef.DimensionUsage) value).source)) //check source is not blank
091                    {
092                        return messages.getString("schemaTreeCellRenderer.sourceMustBeSet.alert", "Source must be set");
093                    }
094                    // check source is name of one of dimensions of schema (shared dimensions)
095                    MondrianGuiDef.Schema s = (MondrianGuiDef.Schema) treeModel.getRoot();
096                    MondrianGuiDef.Dimension ds[] = s.dimensions;
097                    String sourcename = ((MondrianGuiDef.DimensionUsage) value).source;
098                    boolean notfound = true;
099                    for (int j = 0; j < ds.length; j++) {
100                        if (ds[j].name.equalsIgnoreCase(sourcename)) {
101                            notfound = false;
102                            break;
103                        }
104                    }
105                    if (notfound) {
106                        return messages.getFormattedString(
107                                "schemaTreeCellRenderer.sourceInSharedDimensionDoesNotExist.alert",
108                                "Source {0} does not exist as Shared Dimension of Schema", new String[] { sourcename });
109                    }
110                }
111                if (value instanceof MondrianGuiDef.Dimension && cube != null) {
112                    /* //foreignkey can be blank if  hierarchy relation is null
113                     * // this check moved to child hierarchies relation check below
114                     */
115                    if (!isEmpty(((MondrianGuiDef.Dimension) value).foreignKey)) {
116                        // database validity check, if database connection is successful
117                        if (jdbcValidator.isInitialized()) {
118    
119                            String foreignKey = ((MondrianGuiDef.Dimension) value).foreignKey;
120                            if (!jdbcValidator.isColExists(((MondrianGuiDef.Table) cube.fact).schema,
121                                    ((MondrianGuiDef.Table) cube.fact).name, foreignKey)) {
122                                return messages.getFormattedString("schemaTreeCellRenderer.foreignKeyDoesNotExist.alert",
123                                        "foreignKey {0} does not exist in fact table", new String[] { foreignKey });
124                            }
125                        }
126                    }
127                }
128            } else if (value instanceof MondrianGuiDef.Level) {
129                /*
130                // check 'column' exists in 'table' if table is specified otherwise :: case of join
131                // it should exist in relation table if it is specified otherwise   :: case of table
132                // it should exist in fact table  :: case of degenerate dimension where dimension columns exist in fact table
133                // and there is no separate table
134                 */
135                MondrianGuiDef.Level l = (MondrianGuiDef.Level) value;
136                if (!isEmpty(l.levelType)) {
137                    // empty leveltype is treated as default value of "Regular"" which is ok with standard/time dimension
138                    if (parentDimension != null) {
139                        if ((isEmpty(parentDimension.type) || parentDimension.type.equals("StandardDimension"))
140                                && !isEmpty(l.levelType)
141                                && (!l.levelType.equals(MondrianGuiDef.Level._levelType_values[0]))) {
142                            // if dimension type is 'standard' then leveltype should be 'regular'
143                            return messages.getFormattedString("schemaTreeCellRenderer.levelUsedOnlyInTimeDimension.alert",
144                                    "levelType {0} can only be used with a TimeDimension", new String[] { l.levelType });
145                        } else if (!isEmpty(parentDimension.type) && (parentDimension.type.equals("TimeDimension"))
146                                && !isEmpty(l.levelType) && (l.levelType.equals(MondrianGuiDef.Level._levelType_values[0]))) {
147                            // if dimension type is 'time' then leveltype value could be 'timeyears', 'timedays' etc'
148                            return messages
149                                    .getFormattedString("schemaTreeCellRenderer.levelUsedOnlyInStandardDimension.alert",
150                                            "levelType {0} can only be used with a StandardDimension",
151                                            new String[] { l.levelType });
152                        }
153                    }
154                }
155                String column = l.column; // check level's column is in fact table'
156                /* // level column may be blank, if it has properties defined with cols.
157                if (isEmpty(column)) {
158                    return "Column" + emptyMsg;
159                }
160                 */
161                if (isEmpty(column)) {
162                    if (l.properties == null || l.properties.length == 0) {
163                        return messages.getString("schemaTreeCellRenderer.columnMustBeSet.alert", "Column must be set");
164                    }
165                } else {
166                    // database validity check, if database connection is successful
167                    if (jdbcValidator.isInitialized()) {
168                        String table = l.table; // specified table for level's column'
169                        if (isEmpty(table)) {
170                            if (parentHierarchy != null) {
171                                if (parentHierarchy.relation == null && cube != null) { // case of degenerate dimension within cube, hierarchy table not specified
172                                    if (!jdbcValidator.isColExists(((MondrianGuiDef.Table) cube.fact).schema,
173                                            ((MondrianGuiDef.Table) cube.fact).name, column)) {
174                                        return messages
175                                                .getFormattedString(
176                                                        "schemaTreeCellRenderer.degenDimensionColumnDoesNotExist.alert",
177                                                        "Degenerate dimension validation check - Column {0} does not exist in fact table",
178                                                        new String[] { column });
179                                    }
180                                } else if (parentHierarchy.relation instanceof MondrianGuiDef.Table) {
181                                    if (!jdbcValidator.isColExists(
182                                            ((MondrianGuiDef.Table) parentHierarchy.relation).schema,
183                                            ((MondrianGuiDef.Table) parentHierarchy.relation).name, column)) {
184                                        return messages.getFormattedString(
185                                                "schemaTreeCellRenderer.columnInDimensionDoesNotExist.alert",
186                                                "Column {0} does not exist in Dimension table",
187                                                new String[] { ((MondrianGuiDef.Table) parentHierarchy.relation).name });
188                                    }
189                                } else if (parentHierarchy.relation instanceof MondrianGuiDef.Join) { // relation is join, table should be specified
190                                    return messages.getString("schemaTreeCellRenderer.tableMustBeSet.alert",
191                                            "Table must be set");
192                                }
193                            }
194                        } else {
195                            if (!jdbcValidator.isColExists(null, table, column)) {
196                                return messages.getFormattedString(
197                                        "schemaTreeCellRenderer.columnInTableDoesNotExist.alert",
198                                        "Column {0} does not exist in table {1}", new String[] { column, table });
199                            }
200                        }
201                    }
202                }
203            } else if (value instanceof MondrianGuiDef.Property) {
204                /*
205                // check 'column' exists in 'table' if [level table] is specified otherwise :: case of join
206                // it should exist in [hierarchy relation table] if it is specified otherwise   :: case of table
207                // it should exist in [fact table]  :: case of degenerate dimension where dimension columns exist in fact table
208                // and there is no separate table
209                 */
210                MondrianGuiDef.Property p = (MondrianGuiDef.Property) value;
211                String column = p.column; // check property's column is in table'
212                if (isEmpty(column)) {
213                    return messages.getString("schemaTreeCellRenderer.columnMustBeSet.alert", "Column must be set");
214                }
215                // database validity check, if database connection is successful
216                if (jdbcValidator.isInitialized()) {
217                    String table = null;
218                    if (parentLevel != null) {
219                        table = parentLevel.table; // specified table for level's column'
220                    }
221                    if (isEmpty(table)) {
222                        if (parentHierarchy != null) {
223                            if (parentHierarchy.relation == null && cube != null) { // case of degenerate dimension within cube, hierarchy table not specified
224                                if (!jdbcValidator.isColExists(((MondrianGuiDef.Table) cube.fact).schema,
225                                        ((MondrianGuiDef.Table) cube.fact).name, column)) {
226                                    return messages
227                                            .getFormattedString(
228                                                    "schemaTreeCellRenderer.degenDimensionColumnDoesNotExist.alert",
229                                                    "Degenerate dimension validation check - Column {0} does not exist in fact table",
230                                                    new String[] { column });
231                                }
232                            } else if (parentHierarchy.relation instanceof MondrianGuiDef.Table) {
233                                if (!jdbcValidator.isColExists(((MondrianGuiDef.Table) parentHierarchy.relation).schema,
234                                        ((MondrianGuiDef.Table) parentHierarchy.relation).name, column)) {
235                                    return messages.getFormattedString(
236                                            "schemaTreeCellRenderer.columnInDimensionDoesNotExist.alert",
237                                            "Column {0} does not exist in Dimension table",
238                                            new String[] { ((MondrianGuiDef.Table) parentHierarchy.relation).name });
239                                }
240                            }
241                        }
242                    } else {
243                        if (!jdbcValidator.isColExists(null, table, column)) {
244                            return messages.getFormattedString(
245                                    "schemaTreeCellRenderer.columnInDimensionDoesNotExist.alert",
246                                    "Column {0} does not exist in Level table {1}", new String[] { column, table });
247                        }
248                    }
249                }
250            } else if (value instanceof MondrianGuiDef.Measure) {
251                if (isEmpty(((MondrianGuiDef.Measure) value).name)) {
252                    return nameMustBeSet;
253                }
254                if (isEmpty(((MondrianGuiDef.Measure) value).aggregator)) {
255                    return messages.getString("schemaTreeCellRenderer.aggregatorMustBeSet.alert", "Aggregator must be set");
256                }
257                if (((MondrianGuiDef.Measure) value).measureExp != null) {
258                    // Measure expressions are OK
259                } else if (isEmpty(((MondrianGuiDef.Measure) value).column)) {
260                    return messages.getString("schemaTreeCellRenderer.columnMustBeSet.alert", "Column must be set");
261                } else if (cube != null && cube.fact != null) {
262    
263                    // database validity check, if database connection is successful
264                    if (jdbcValidator.isInitialized()) {
265    
266                        //Vector allcols  = jdbcMetaData.getAllColumns(((MondrianGuiDef.Table) cube.fact).schema, ((MondrianGuiDef.Table) cube.fact).name);
267    
268                        String column = ((MondrianGuiDef.Measure) value).column;
269                        if (jdbcValidator.isColExists(((MondrianGuiDef.Table) cube.fact).schema,
270                                ((MondrianGuiDef.Table) cube.fact).name, column)) {
271                            /* disabled check that the column value should exist in table because column could also be an expression
272                            if (! jdbcMetaData.isColExists(((MondrianGuiDef.Table) cube.fact).schema, ((MondrianGuiDef.Table) cube.fact).name, column)) {
273                            return "Column '"+column+"' does not exist in fact table.";
274                            }
275                             */
276                            /*
277                            if (! allcols.contains(column))        // check foreignKey is a fact table column
278                            {   return "Column '"+column+"' does not exist in fact table.";}
279                             */
280                            // check for aggregator type only if column exists in table
281                            // check if aggregator selected is valid on the data type of the column selected.
282                            int colType = jdbcValidator.getColumnDataType(((MondrianGuiDef.Table) cube.fact).schema,
283                                    ((MondrianGuiDef.Table) cube.fact).name, ((MondrianGuiDef.Measure) value).column);
284                            // colType of 2, 4,5, 7,8 is numeric types whereas 1, 12 are char varchar string and 91 is date type
285                            int agIndex = -1;
286                            if ("sum".equals(((MondrianGuiDef.Measure) value).aggregator)
287                                    || "avg".equals(((MondrianGuiDef.Measure) value).aggregator)) {
288                                agIndex = 0; // aggregator = sum or avg, column should be numeric
289                            }
290                            if (!(agIndex == -1 || (colType >= 2 && colType <= 8))) {
291                                return messages.getFormattedString(
292                                        "schemaTreeCellRenderer.aggregatorNotValidForColumn.alert",
293                                        "Aggregator {0} is not valid for the data type of the column {1}", new String[] {
294                                                ((MondrianGuiDef.Measure) value).aggregator,
295                                                ((MondrianGuiDef.Measure) value).column });
296                            }
297                        }
298                    }
299                }
300            } else if (value instanceof MondrianGuiDef.Hierarchy) {
301                if (((MondrianGuiDef.Hierarchy) value).relation instanceof MondrianGuiDef.Join) {
302                    if (isEmpty(((MondrianGuiDef.Hierarchy) value).primaryKeyTable)) {
303                        if (isEmpty(((MondrianGuiDef.Hierarchy) value).primaryKey)) {
304                            return messages.getString("schemaTreeCellRenderer.primaryKeyTableAndPrimaryKeyMustBeSet.alert",
305                                    "PrimaryKeyTable and PrimaryKey must be set for Join");
306                        } else {
307                            return messages.getString("schemaTreeCellRenderer.primaryKeyTableMustBeSet.alert",
308                                    "PrimaryKeyTable must be set for Join");
309                        }
310                    }
311                    if (isEmpty(((MondrianGuiDef.Hierarchy) value).primaryKey)) {
312                        return messages.getString("schemaTreeCellRenderer.primaryKeyMustBeSet.alert",
313                                "PrimaryKey must be set for Join");
314                    }
315                }
316            } else if (value instanceof MondrianGuiDef.NamedSet) {
317                if (isEmpty(((MondrianGuiDef.NamedSet) value).name)) {
318                    return nameMustBeSet;
319                }
320                if (isEmpty(((MondrianGuiDef.NamedSet) value).formula)) {
321                    return messages.getString("schemaTreeCellRenderer.formulaMustBeSet.alert", "Formula must be set");
322                }
323            } else if (value instanceof MondrianGuiDef.UserDefinedFunction) {
324                if (isEmpty(((MondrianGuiDef.UserDefinedFunction) value).name)) {
325                    return nameMustBeSet;
326                }
327                if (isEmpty(((MondrianGuiDef.UserDefinedFunction) value).className)) {
328                    return messages.getString("schemaTreeCellRenderer.classNameMustBeSet.alert", "Class name must be set");
329                }
330            } else if (value instanceof MondrianGuiDef.CalculatedMember) {
331                if (isEmpty(((MondrianGuiDef.CalculatedMember) value).name)) {
332                    return nameMustBeSet;
333                }
334                if (isEmpty(((MondrianGuiDef.CalculatedMember) value).dimension)) {
335                    return messages.getString("schemaTreeCellRenderer.dimensionMustBeSet.alert", "Dimension must be set");
336                }
337            } else if (value instanceof MondrianGuiDef.Join) {
338                if (isEmpty(((MondrianGuiDef.Join) value).leftKey)) {
339                    return messages.getString("schemaTreeCellRenderer.leftKeyMustBeSet.alert", "Left key must be set");
340                }
341                if (isEmpty(((MondrianGuiDef.Join) value).rightKey)) {
342                    return messages.getString("schemaTreeCellRenderer.rightKeyMustBeSet.alert", "Right key must be set");
343                }
344            }
345    
346            // Step 2: check validity of all child objects for this value object.
347            int childCnt = treeModel.getChildCount(value);
348            for (int i = 0; i < childCnt; i++) {
349                Object child = treeModel.getChild(value, i);
350                String childErrMsg;
351                if (child instanceof MondrianGuiDef.Cube) {
352                    childErrMsg = invalid(messages, jdbcValidator, treeModel, tpath, child, child, parentDimension,
353                            parentHierarchy, parentLevel); //check current cube child and its children
354                } else if (child instanceof MondrianGuiDef.Dimension) {
355                    childErrMsg = invalid(messages, jdbcValidator, treeModel, tpath, child, cube, child, parentHierarchy,
356                            parentLevel); //check the current hierarchy and its children
357                } else if (child instanceof MondrianGuiDef.Hierarchy) {
358                    // special check for cube dimension where foreign key is blank : allowed /not allowed
359                    if (value instanceof MondrianGuiDef.Dimension && cube != null
360                            && ((MondrianGuiDef.Hierarchy) child).relation != null) {
361                        if (isEmpty(((MondrianGuiDef.Dimension) value).foreignKey)) //check foreignkey is not blank
362                        {
363                            // if relation is null, foreignkey must be specified
364    
365                            return messages.getString("schemaTreeCellRenderer.foreignKeyMustBeSet.alert",
366                                    "Foreign key must be set");
367                        }
368                    }
369                    childErrMsg = invalid(messages, jdbcValidator, treeModel, tpath, child, cube, parentDimension, child,
370                            parentLevel); //check the current hierarchy and its children
371                } else if (child instanceof MondrianGuiDef.Level) {
372                    childErrMsg = invalid(messages, jdbcValidator, treeModel, tpath, child, cube, parentDimension,
373                            parentHierarchy, child); //check the current hierarchy and its children
374                } else {
375                    childErrMsg = invalid(messages, jdbcValidator, treeModel, tpath, child, cube, parentDimension,
376                            parentHierarchy, parentLevel); //check this child and all its children objects with incoming cube and hierarchy
377                }
378    
379                /* If all children are valid then do a special check.
380                 * Special check for cubes to see if their child dimensions have foreign key set and set the childErrMsg with error msg
381                 */
382                /* === Begin : disabled
383                if (childErrMsg == null) {  // all children are valid
384                    if (child instanceof MondrianGuiDef.Cube) {
385                        MondrianGuiDef.Cube c = (MondrianGuiDef.Cube) child;
386                        MondrianGuiDef.CubeDimension [] ds = c.dimensions;
387                        for (int j=0; j<ds.length; j++) {
388                            MondrianGuiDef.CubeDimension d = (MondrianGuiDef.CubeDimension) ds[j];
389                            if (d instanceof MondrianGuiDef.DimensionUsage) {
390                                continue;   // check the next dimension.
391                            }
392    
393                            if(isEmpty(d.foreignKey))    //check foreignkey is not blank
394                            { childErrMsg = "ForeignKey" + emptyMsg;
395                              break;
396                            }
397    
398                            // database validity check, if database connection is successful
399                            if (jdbcMetaData.getErrMsg() == null) {
400    
401                                //Vector allcols  = jdbcMetaData.getAllColumns(((MondrianGuiDef.Table) c.fact).schema, ((MondrianGuiDef.Table) c.fact).name);
402                                String foreignKey = d.foreignKey;
403                                if (! jdbcMetaData.isColExists(((MondrianGuiDef.Table) c.fact).schema, ((MondrianGuiDef.Table) c.fact).name, foreignKey)) {
404                                    childErrMsg = "ForeignKey '"+foreignKey+"' does not exist in fact table.";
405                                    break;
406                                }
407                                /*
408                                if (! allcols.contains(foreignKey))        // check foreignKey is a fact table column
409                                {   childErrMsg = "ForeignKey '"+foreignKey+"' does not exist in fact table.";
410                                    break;
411                                }
412                 * /
413                            }
414                        }
415                    }
416                }
417                 * === End : disabled
418                 */
419                // Now set the final errormsg
420                if (childErrMsg != null) {
421                    String childClassName = child.getClass().getName();
422                    String simpleName[] = childClassName.split("[$.]", 0);
423                    String childName;
424                    try {
425                        Field f = child.getClass().getField("name");
426                        childName = (String) f.get(child);
427                        if (childName == null) {
428                            childName = "";
429                        }
430                        childErrMsg = messages.getFormattedString("schemaTreeCellRenderer.childErrorMessageWithName.alert",
431                                "{0} {1} is invalid", new String[] { simpleName[simpleName.length - 1], childName });
432    
433                    } catch (Exception ex) {
434                        childErrMsg = messages.getFormattedString(
435                                "schemaTreeCellRenderer.childErrorExceptionMessage.alert", "{0} is invalid",
436                                new String[] { simpleName[simpleName.length - 1] });
437                    }
438                    return childErrMsg;
439                }
440            }
441    
442            return null;
443        }
444    
445        private static boolean isEmpty(Object v) {
446            if ((v == null) || v.equals("")) {
447                return true;
448            } else {
449                return false;
450            }
451        }
452    
453    }
454    
455    // End ValidationUtils.java