001    /*
002    // $Id: //open/mondrian/src/main/mondrian/rolap/RolapCell.java#28 $
003    // This software is subject to the terms of the Common Public License
004    // Agreement, available at the following URL:
005    // http://www.opensource.org/licenses/cpl.html.
006    // Copyright (C) 2005-2008 Julian Hyde
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    */
010    package mondrian.rolap;
011    
012    import mondrian.mdx.*;
013    import mondrian.olap.*;
014    import mondrian.rolap.agg.AggregationManager;
015    import mondrian.rolap.agg.CellRequest;
016    
017    import java.sql.*;
018    import java.util.List;
019    import java.util.ArrayList;
020    
021    /**
022     * <code>RolapCell</code> implements {@link mondrian.olap.Cell} within a
023     * {@link RolapResult}.
024     *
025     * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapCell.java#28 $
026     */
027    class RolapCell implements Cell {
028        private final RolapResult result;
029        protected final int[] pos;
030        protected RolapResult.CellInfo ci;
031    
032        RolapCell(RolapResult result, int[] pos, RolapResult.CellInfo ci) {
033            this.result = result;
034            this.pos = pos;
035            this.ci = ci;
036        }
037    
038        public Object getValue() {
039            return ci.value;
040        }
041    
042        public String getCachedFormatString() {
043            return ci.formatString;
044        }
045    
046        public String getFormattedValue() {
047            return ci.getFormatValue();
048        }
049    
050        public boolean isNull() {
051            return (ci.value == Util.nullValue);
052        }
053    
054        public boolean isError() {
055            return (ci.value instanceof Throwable);
056        }
057    
058        /**
059         * Create an sql query that, when executed, will return the drill through
060         * data for this cell. If the parameter extendedContext is true, then the
061         * query will include all the levels (i.e. columns) of non-constraining
062         * members (i.e. members which are at the "All" level).
063         * If the parameter extendedContext is false, the query will exclude
064         * the levels (coulmns) of non-constraining members.
065         */
066        public String getDrillThroughSQL(boolean extendedContext) {
067            RolapAggregationManager aggMan = AggregationManager.instance();
068            final Member[] currentMembers = getMembersForDrillThrough();
069            CellRequest cellRequest =
070                RolapAggregationManager.makeDrillThroughRequest(
071                    currentMembers, extendedContext, result.getCube());
072            return (cellRequest == null)
073                ? null
074                : aggMan.getDrillThroughSql(cellRequest, false);
075        }
076    
077    
078        public int getDrillThroughCount() {
079            RolapAggregationManager aggMan = AggregationManager.instance();
080            final Member[] currentMembers = getMembersForDrillThrough();
081            CellRequest cellRequest =
082                RolapAggregationManager.makeDrillThroughRequest(
083                    currentMembers, false, result.getCube());
084            if (cellRequest == null) {
085                return -1;
086            }
087            RolapConnection connection =
088                (RolapConnection) result.getQuery().getConnection();
089            final String sql = aggMan.getDrillThroughSql(cellRequest, true);
090            final SqlStatement stmt =
091                RolapUtil.executeQuery(
092                    connection.getDataSource(),
093                    sql,
094                    "RolapCell.getDrillThroughCount",
095                    "Error while counting drill-through");
096            try {
097                ResultSet rs = stmt.getResultSet();
098                rs.next();
099                ++stmt.rowCount;
100                return rs.getInt(1);
101            } catch (SQLException e) {
102                throw stmt.handle(e);
103            } finally {
104                stmt.close();
105            }
106        }
107    
108        /**
109         * Returns whether it is possible to drill through this cell.
110         * Drill-through is possible if the measure is a stored measure
111         * and not possible for calculated measures.
112         *
113         * @return true if can drill through
114         */
115        public boolean canDrillThrough() {
116            // get current members
117            final Member[] currentMembers = getMembersForDrillThrough();
118            Cube x = chooseDrillThroughCube(currentMembers, result.getCube());
119            return x != null;
120        }
121    
122        public static RolapCube chooseDrillThroughCube(
123            Member[] currentMembers,
124            RolapCube defaultCube)
125        {
126            if (defaultCube != null && defaultCube.isVirtual()) {
127                List<RolapCube> cubes = new ArrayList<RolapCube>();
128                for (RolapMember member : defaultCube.getMeasuresMembers()) {
129                    if (member instanceof RolapVirtualCubeMeasure) {
130                        RolapVirtualCubeMeasure measure =
131                            (RolapVirtualCubeMeasure) member;
132                        cubes.add(measure.getCube());
133                    }
134                }
135                defaultCube = cubes.get(0);
136                assert !defaultCube.isVirtual();
137            }
138            final DrillThroughVisitor visitor =
139                new DrillThroughVisitor();
140            try {
141                for (Member member : currentMembers) {
142                    visitor.handleMember(member);
143                }
144            } catch (RuntimeException e) {
145                if (e == DrillThroughVisitor.bomb) {
146                    // No cubes left
147                    return null;
148                } else {
149                    throw e;
150                }
151            }
152            return visitor.cube == null
153                 ? defaultCube
154                 : visitor.cube;
155        }
156    
157        private RolapEvaluator getEvaluator() {
158            return result.getCellEvaluator(pos);
159        }
160    
161        private Member[] getMembersForDrillThrough() {
162            final Member[] currentMembers = result.getCellMembers(pos);
163    
164            // replace member if we're dealing with a trivial formula
165            if (currentMembers[0] instanceof RolapHierarchy.RolapCalculatedMeasure) {
166                RolapHierarchy.RolapCalculatedMeasure measure =
167                    (RolapHierarchy.RolapCalculatedMeasure)currentMembers[0];
168                if (measure.getFormula().getExpression() instanceof MemberExpr) {
169                    currentMembers[0] =
170                        ((MemberExpr)measure.getFormula().getExpression()).getMember();
171                }
172            }
173            return currentMembers;
174        }
175    
176        public Object getPropertyValue(String propertyName) {
177            final boolean matchCase =
178                MondrianProperties.instance().CaseSensitive.get();
179            Property property = Property.lookup(propertyName, matchCase);
180            Object defaultValue = null;
181            if (property != null) {
182                switch (property.ordinal) {
183                case Property.CELL_ORDINAL_ORDINAL:
184                    return result.getCellOrdinal(pos);
185                case Property.VALUE_ORDINAL:
186                    return getValue();
187                case Property.FORMAT_STRING_ORDINAL:
188                    if (ci.formatString == null) {
189                        ci.formatString = getEvaluator().getFormatString();
190                    }
191                    return ci.formatString;
192                case Property.FORMATTED_VALUE_ORDINAL:
193                    return getFormattedValue();
194                case Property.FONT_FLAGS_ORDINAL:
195                    defaultValue = 0;
196                    break;
197                case Property.SOLVE_ORDER_ORDINAL:
198                    defaultValue = 0;
199                    break;
200                default:
201                    // fall through
202                }
203            }
204            return getEvaluator().getProperty(propertyName, defaultValue);
205        }
206    
207        public Member getContextMember(Dimension dimension) {
208            return result.getMember(pos, dimension);
209        }
210    
211        /**
212         * Visitor that walks over a cell's expression and checks whether the
213         * cell should allow drill-through. If not, throws the {@link #bomb}
214         * exception.
215         *
216         * <p>Examples:</p>
217         * <ul>
218         * <li>Literal 1 is drillable</li>
219         * <li>Member [Measures].[Unit Sales] is drillable</li>
220         * <li>Calculated member with expression [Measures].[Unit Sales] + 1 is drillable</li>
221         * <li>Calculated member with expression
222         *     ([Measures].[Unit Sales], [Time].PrevMember) is not drillable</li>
223         * </ul>
224         */
225        private static class DrillThroughVisitor extends MdxVisitorImpl {
226            static final RuntimeException bomb = new RuntimeException();
227            RolapCube cube = null;
228    
229            DrillThroughVisitor() {
230            }
231    
232            public Object visit(MemberExpr memberExpr) {
233                handleMember(memberExpr.getMember());
234                return null;
235            }
236    
237            public Object visit(ResolvedFunCall call) {
238                final FunDef def = call.getFunDef();
239                final Exp[] args = call.getArgs();
240                if (def.getName().equals("+")
241                    || def.getName().equals("-")
242                    || def.getName().equals("/")
243                    || def.getName().equals("*")
244                    || def.getName().equals("CoalesceEmpty")
245                    // Allow parentheses but don't allow tuple
246                    || def.getName().equals("()") && args.length == 1) {
247                    visitChildren(args);
248                    return null;
249                }
250                throw bomb;
251            }
252    
253            private void visitChildren(Exp[] args) {
254                for (Exp arg : args) {
255                    arg.accept(this);
256                }
257            }
258    
259            public void handleMember(Member member) {
260                if (member instanceof RolapStoredMeasure) {
261                    // If this member is in a different cube that previous members
262                    // we've seen, we cannot drill through.
263                    final RolapCube cube = ((RolapStoredMeasure) member).getCube();
264                    if (this.cube == null) {
265                        this.cube = cube;
266                    } else if (this.cube != cube) {
267                        // this measure lives in a different cube than previous
268                        // measures we have seen
269                        throw bomb;
270                    }
271                } else if (member instanceof RolapCubeMember) {
272                    handleMember(((RolapCubeMember) member).rolapMember);
273                } else if (member instanceof RolapHierarchy.RolapCalculatedMeasure) {
274                    RolapHierarchy.RolapCalculatedMeasure measure =
275                        (RolapHierarchy.RolapCalculatedMeasure) member;
276                    measure.getFormula().getExpression().accept(this);
277                } else if (member instanceof RolapMember) {
278                    // regular RolapMember - fine
279                } else {
280                    // don't know what this is!
281                    throw bomb;
282                }
283            }
284    
285            public Object visit(NamedSetExpr namedSetExpr) {
286                throw Util.newInternal("not valid here: " + namedSetExpr);
287            }
288    
289            public Object visit(Literal literal) {
290                return null; // literals are drillable
291            }
292    
293            public Object visit(Query query) {
294                throw Util.newInternal("not valid here: " + query);
295            }
296    
297            public Object visit(QueryAxis queryAxis) {
298                throw Util.newInternal("not valid here: " + queryAxis);
299            }
300    
301            public Object visit(Formula formula) {
302                throw Util.newInternal("not valid here: " + formula);
303            }
304    
305            public Object visit(UnresolvedFunCall call) {
306                throw Util.newInternal("expected resolved expression");
307            }
308    
309            public Object visit(Id id) {
310                throw Util.newInternal("expected resolved expression");
311            }
312    
313            public Object visit(ParameterExpr parameterExpr) {
314                // Not valid in general; might contain complex expression
315                throw bomb;
316            }
317    
318            public Object visit(DimensionExpr dimensionExpr) {
319                // Not valid in general; might be part of complex expression
320                throw bomb;
321            }
322    
323            public Object visit(HierarchyExpr hierarchyExpr) {
324                // Not valid in general; might be part of complex expression
325                throw bomb;
326            }
327    
328            public Object visit(LevelExpr levelExpr) {
329                // Not valid in general; might be part of complex expression
330                throw bomb;
331            }
332        }
333    }
334    
335    // End RolapCell.java