001 /*
002 // $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/RolapSchemaReader.java#3 $
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) 2003-2007 Julian Hyde
007 // All Rights Reserved.
008 // You must accept the terms of that agreement to use this software.
009 //
010 // jhyde, Feb 24, 2003
011 */
012 package mondrian.rolap;
013
014 import java.util.ArrayList;
015 import java.util.Arrays;
016 import java.util.Collections;
017 import java.util.HashMap;
018 import java.util.List;
019 import java.util.Map;
020
021 import javax.sql.DataSource;
022
023 import mondrian.olap.*;
024 import mondrian.olap.type.StringType;
025 import mondrian.rolap.sql.TupleConstraint;
026 import mondrian.rolap.sql.MemberChildrenConstraint;
027 import mondrian.calc.Calc;
028 import mondrian.calc.ExpCompiler;
029 import mondrian.calc.DummyExp;
030 import mondrian.calc.impl.AbstractCalc;
031 import mondrian.calc.impl.GenericCalc;
032
033 import org.apache.log4j.Logger;
034 import org.eigenbase.util.property.Property;
035
036 /**
037 * A <code>RolapSchemaReader</code> allows you to read schema objects while
038 * observing the access-control profile specified by a given role.
039 *
040 * @author jhyde
041 * @since Feb 24, 2003
042 * @version $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/RolapSchemaReader.java#3 $
043 */
044 public abstract class RolapSchemaReader
045 implements SchemaReader, RolapNativeSet.SchemaReaderWithMemberReaderAvailable {
046 private final Role role;
047 private final Map<Hierarchy, MemberReader> hierarchyReaders =
048 new HashMap<Hierarchy, MemberReader>();
049 private final RolapSchema schema;
050 private final SqlConstraintFactory sqlConstraintFactory =
051 SqlConstraintFactory.instance();
052 private static final Logger LOGGER =
053 Logger.getLogger(RolapSchemaReader.class);
054
055 RolapSchemaReader(Role role, RolapSchema schema) {
056 assert role != null : "precondition: role != null";
057 this.role = role;
058 this.schema = schema;
059 }
060
061 public Role getRole() {
062 return role;
063 }
064
065 public Member[] getHierarchyRootMembers(Hierarchy hierarchy)
066 {
067 final Role.HierarchyAccess hierarchyAccess =
068 role.getAccessDetails(hierarchy);
069 final Level[] levels = hierarchy.getLevels();
070 final Level firstLevel;
071 if (hierarchyAccess == null) {
072 firstLevel = levels[0];
073 } else {
074 firstLevel = levels[hierarchyAccess.getTopLevelDepth()];
075 }
076 return getLevelMembers(firstLevel, true);
077 }
078
079 public synchronized MemberReader getMemberReader(Hierarchy hierarchy) {
080 MemberReader memberReader = hierarchyReaders.get(hierarchy);
081 if (memberReader == null) {
082 memberReader = ((RolapHierarchy) hierarchy).createMemberReader(role);
083 hierarchyReaders.put(hierarchy, memberReader);
084 }
085 return memberReader;
086 }
087
088 public Member substitute(Member member) {
089 final MemberReader memberReader =
090 getMemberReader(member.getHierarchy());
091 return memberReader.substitute((RolapMember) member);
092 }
093
094 public void getMemberRange(
095 Level level, Member startMember, Member endMember, List<Member> list)
096 {
097 getMemberReader(level.getHierarchy()).getMemberRange(
098 (RolapLevel) level, (RolapMember) startMember,
099 (RolapMember) endMember, Util.<RolapMember>cast(list));
100 }
101
102 public int compareMembersHierarchically(Member m1, Member m2) {
103 RolapMember member1 = (RolapMember) m1;
104 RolapMember member2 = (RolapMember) m2;
105 final RolapHierarchy hierarchy = member1.getHierarchy();
106 Util.assertPrecondition(hierarchy == m2.getHierarchy());
107 return getMemberReader(hierarchy).compare(member1, member2, true);
108 }
109
110 public Member getMemberParent(Member member) {
111 return getMemberReader(member.getHierarchy()).getMemberParent(
112 (RolapMember) member);
113 }
114
115 public int getMemberDepth(Member member) {
116 final Role.HierarchyAccess hierarchyAccess = role.getAccessDetails(member.getHierarchy());
117 if (hierarchyAccess != null) {
118 final int memberDepth = member.getLevel().getDepth();
119 final int topLevelDepth = hierarchyAccess.getTopLevelDepth();
120 return memberDepth - topLevelDepth;
121 } else if (((RolapLevel) member.getLevel()).isParentChild()) {
122 // For members of parent-child hierarchy, members in the same level
123 // may have different depths.
124 int depth = 0;
125 for (Member m = member.getParentMember();
126 m != null;
127 m = m.getParentMember())
128 {
129 depth++;
130 }
131 return depth;
132 } else {
133 return member.getLevel().getDepth();
134 }
135 }
136
137
138 public Member[] getMemberChildren(Member member) {
139 return getMemberChildren(member, null);
140 }
141
142 public Member[] getMemberChildren(Member member, Evaluator context) {
143 MemberChildrenConstraint constraint =
144 sqlConstraintFactory.getMemberChildrenConstraint(context);
145 List<RolapMember> memberList =
146 internalGetMemberChildren(member, constraint);
147 return memberList.toArray(new Member[memberList.size()]);
148 }
149
150 private List<RolapMember> internalGetMemberChildren(
151 Member member, MemberChildrenConstraint constraint) {
152 List<RolapMember> children = new ArrayList<RolapMember>();
153 final Hierarchy hierarchy = member.getHierarchy();
154 final MemberReader memberReader = getMemberReader(hierarchy);
155 memberReader.getMemberChildren(
156 (RolapMember) member, children, constraint);
157 return children;
158 }
159
160 /**
161 * check, whether members children are cached, and
162 * if yes - return children count
163 * if no - return -1
164 */
165 public int getChildrenCountFromCache(Member member) {
166 final Hierarchy hierarchy = member.getHierarchy();
167 final MemberReader memberReader = getMemberReader(hierarchy);
168 if (memberReader instanceof
169 RolapCubeHierarchy.RolapCubeHierarchyMemberReader) {
170 List list =
171 ((RolapCubeHierarchy.RolapCubeHierarchyMemberReader)memberReader)
172 .getRolapCubeMemberCacheHelper()
173 .getChildrenFromCache((RolapMember)member, null);
174 if (list == null) {
175 return -1;
176 }
177 return list.size();
178 }
179
180 if (memberReader instanceof SmartMemberReader) {
181 List list = ((SmartMemberReader)memberReader).getMemberCache()
182 .getChildrenFromCache((RolapMember)member, null);
183 if (list == null) {
184 return -1;
185 }
186 return list.size();
187 }
188 if( !(memberReader instanceof MemberCache)) {
189 return -1;
190 }
191 List list = ((MemberCache)memberReader)
192 .getChildrenFromCache((RolapMember)member, null);
193 if (list == null) {
194 return -1;
195 }
196 return list.size();
197 }
198
199 /**
200 * Returns number of members in a level,
201 * if the information can be retrieved from cache.
202 * Otherwise {@link Integer#MIN_VALUE}.
203 *
204 * @param level Level
205 * @return number of members in level
206 */
207 private int getLevelCardinalityFromCache(Level level) {
208 final Hierarchy hierarchy = level.getHierarchy();
209 final MemberReader memberReader = getMemberReader(hierarchy);
210 if (memberReader instanceof
211 RolapCubeHierarchy.RolapCubeHierarchyMemberReader) {
212 List list =
213 ((RolapCubeHierarchy.RolapCubeHierarchyMemberReader)memberReader)
214 .getRolapCubeMemberCacheHelper()
215 .getLevelMembersFromCache((RolapLevel) level, null);
216 if (list == null) {
217 return Integer.MIN_VALUE;
218 }
219 return list.size();
220 }
221
222 if (memberReader instanceof SmartMemberReader) {
223 List list = ((SmartMemberReader)memberReader).getMemberCache()
224 .getLevelMembersFromCache((RolapLevel) level, null);
225 if (list == null) {
226 return Integer.MIN_VALUE;
227 }
228 return list.size();
229 }
230
231 if( !(memberReader instanceof MemberCache)) {
232 return Integer.MIN_VALUE;
233 }
234 List list = ((MemberCache)memberReader).getLevelMembersFromCache(
235 (RolapLevel) level, null);
236 if (list == null) {
237 return Integer.MIN_VALUE;
238 }
239 return list.size();
240 }
241
242 public int getLevelCardinality(
243 Level level,
244 boolean approximate,
245 boolean materialize)
246 {
247 if (!this.role.canAccess(level)) {
248 return 1;
249 }
250
251 int rowCount = Integer.MIN_VALUE;
252 if (approximate) {
253 // See if the schema has an approximation.
254 rowCount = level.getApproxRowCount();
255 }
256
257 if (rowCount == Integer.MIN_VALUE) {
258 // See if the precise row count is available in cache.
259 rowCount = getLevelCardinalityFromCache(level);
260 }
261
262 if (rowCount == Integer.MIN_VALUE) {
263 if (materialize) {
264 // Either the approximate row count hasn't been set,
265 // or they want the precise row count.
266 final MemberReader memberReader =
267 getMemberReader(level.getHierarchy());
268 rowCount =
269 memberReader.getLevelMemberCount((RolapLevel) level);
270 // Cache it for future.
271 ((RolapLevel) level).setApproxRowCount(rowCount);
272 }
273 }
274 return rowCount;
275 }
276
277 public Member[] getMemberChildren(Member[] members) {
278 return getMemberChildren(members, null);
279 }
280
281 public Member[] getMemberChildren(Member[] members, Evaluator context) {
282 if (members.length == 0) {
283 return RolapUtil.emptyMemberArray;
284 } else {
285 MemberChildrenConstraint constraint =
286 sqlConstraintFactory.getMemberChildrenConstraint(context);
287 final Hierarchy hierarchy = members[0].getHierarchy();
288 final MemberReader memberReader = getMemberReader(hierarchy);
289 List<RolapMember> children = new ArrayList<RolapMember>();
290 memberReader.getMemberChildren(
291 Util.<RolapMember>cast(Arrays.asList(members)),
292 children,
293 constraint);
294 return RolapUtil.toArray(children);
295 }
296 }
297
298 public abstract Cube getCube();
299
300 public OlapElement getElementChild(OlapElement parent, Id.Segment name) {
301 return getElementChild(parent, name, MatchType.EXACT);
302 }
303
304 public OlapElement getElementChild(
305 OlapElement parent, Id.Segment name, MatchType matchType)
306 {
307 return parent.lookupChild(this, name, matchType);
308 }
309
310 public final Member getMemberByUniqueName(
311 List<Id.Segment> uniqueNameParts,
312 boolean failIfNotFound)
313 {
314 return getMemberByUniqueName(
315 uniqueNameParts, failIfNotFound, MatchType.EXACT);
316 }
317
318 public Member getMemberByUniqueName(
319 List<Id.Segment> uniqueNameParts,
320 boolean failIfNotFound,
321 MatchType matchType)
322 {
323 // In general, this schema reader doesn't have a cube, so we cannot
324 // start looking up members.
325 return null;
326 }
327
328 public OlapElement lookupCompound(
329 OlapElement parent,
330 List<Id.Segment> names,
331 boolean failIfNotFound,
332 int category)
333 {
334 return lookupCompound(
335 parent, names, failIfNotFound, category, MatchType.EXACT);
336 }
337
338 public OlapElement lookupCompound(
339 OlapElement parent,
340 List<Id.Segment> names,
341 boolean failIfNotFound,
342 int category,
343 MatchType matchType)
344 {
345 return Util.lookupCompound(
346 this, parent, names, failIfNotFound, category, matchType);
347 }
348
349 public Member lookupMemberChildByName(Member parent, Id.Segment childName)
350 {
351 return lookupMemberChildByName(parent, childName, MatchType.EXACT);
352 }
353
354 public Member lookupMemberChildByName(
355 Member parent, Id.Segment childName, MatchType matchType)
356 {
357 LOGGER.debug("looking for child \"" + childName + "\" of " + parent);
358 assert !(parent instanceof RolapHierarchy.LimitedRollupMember);
359 if (parent instanceof RolapHierarchy.LimitedRollupMember) {
360 Util.deprecated("removeme");
361 RolapHierarchy.LimitedRollupMember limitedRollupMember =
362 (RolapHierarchy.LimitedRollupMember) parent;
363 parent = limitedRollupMember.member;
364 }
365 try {
366 MemberChildrenConstraint constraint;
367 if (matchType == MatchType.EXACT) {
368 constraint = sqlConstraintFactory.getChildByNameConstraint(
369 (RolapMember) parent, childName);
370 } else {
371 constraint =
372 sqlConstraintFactory.getMemberChildrenConstraint(null);
373 }
374 List<RolapMember> children =
375 internalGetMemberChildren(parent, constraint);
376 if (children.size() > 0) {
377 return
378 RolapUtil.findBestMemberMatch(
379 children,
380 (RolapMember) parent,
381 children.get(0).getLevel(),
382 childName,
383 matchType,
384 true);
385 }
386 } catch (NumberFormatException e) {
387 // this was thrown in SqlQuery#quote(boolean numeric, Object value). This happens when
388 // Mondrian searches for unqualified Olap Elements like [Month], because it tries to look up
389 // a member with that name in all dimensions. Then it generates for example
390 // "select .. from time where year = Month" which will result in a NFE because
391 // "Month" can not be parsed as a number. The real bug is probably, that Mondrian
392 // looks at members at all.
393 //
394 // @see RolapCube#lookupChild()
395 LOGGER.debug("NumberFormatException in lookupMemberChildByName for parent = \"" + parent + "\", childName=\"" + childName + "\", exception: " + e.getMessage());
396 }
397 return null;
398 }
399
400 public Member getCalculatedMember(List<Id.Segment> nameParts) {
401 // There are no calculated members defined against a schema.
402 return null;
403 }
404
405 public NamedSet getNamedSet(List<Id.Segment> nameParts) {
406 if (nameParts.size() != 1) {
407 return null;
408 }
409 final String name = nameParts.get(0).name;
410 return schema.getNamedSet(name);
411 }
412
413 public Member getLeadMember(Member member, int n) {
414 final MemberReader memberReader = getMemberReader(member.getHierarchy());
415 return memberReader.getLeadMember((RolapMember) member, n);
416 }
417
418 public Member[] getLevelMembers(Level level, boolean includeCalculated) {
419 Member[] members = getLevelMembers(level, null);
420 if (!includeCalculated) {
421 members = SqlConstraintUtils.removeCalculatedMembers(members);
422 }
423 return members;
424 }
425
426 public Member[] getLevelMembers(Level level, Evaluator context) {
427 boolean[] satisfied = {false};
428 TupleConstraint constraint =
429 sqlConstraintFactory.getLevelMembersConstraint(
430 context,
431 new Level [] { level },
432 satisfied);
433 final MemberReader memberReader =
434 getMemberReader(level.getHierarchy());
435 List<RolapMember> membersInLevel =
436 memberReader.getMembersInLevel(
437 (RolapLevel) level, 0, Integer.MAX_VALUE, constraint);
438 if (!satisfied[0]) {
439 // Could not satisfy the constraint by generating SQL. Apply the
440 // non-empty constraint manually.
441 final Evaluator evaluator = context.push();
442 List<RolapMember> allMembersInLevel = membersInLevel;
443 membersInLevel = new ArrayList<RolapMember>();
444 for (RolapMember member : allMembersInLevel) {
445 evaluator.setContext(member);
446 if (evaluator.evaluateCurrent() != null) {
447 membersInLevel.add(member);
448 }
449 }
450 }
451 return RolapUtil.toArray(membersInLevel);
452 }
453
454 public Level[] getHierarchyLevels(Hierarchy hierarchy) {
455 assert hierarchy != null;
456 final Role.HierarchyAccess hierarchyAccess = role.getAccessDetails(hierarchy);
457 final Level[] levels = hierarchy.getLevels();
458 if (hierarchyAccess == null) {
459 return levels;
460 }
461 Level topLevel = levels[hierarchyAccess.getTopLevelDepth()];
462 Level bottomLevel = levels[hierarchyAccess.getBottomLevelDepth()];
463 final int levelCount = bottomLevel.getDepth() - topLevel.getDepth() + 1;
464 Level[] restrictedLevels = new Level[levelCount];
465 System.arraycopy(levels, topLevel.getDepth(), restrictedLevels, 0, levelCount);
466 Util.assertPostcondition(restrictedLevels.length >= 1, "return.length >= 1");
467 return restrictedLevels;
468 }
469
470 public Member getHierarchyDefaultMember(Hierarchy hierarchy) {
471 assert hierarchy != null;
472 // If the whole hierarchy is inaccessible, return the intrinsic default
473 // member. This is important to construct a evaluator.
474 if (role.getAccess(hierarchy) == Access.NONE) {
475 return hierarchy.getDefaultMember();
476 }
477 return getMemberReader(hierarchy).getDefaultMember();
478 }
479
480 public boolean isDrillable(Member member) {
481 final RolapLevel level = (RolapLevel) member.getLevel();
482 if (level.getParentExp() != null) {
483 // This is a parent-child level, so its children, if any, come from
484 // the same level.
485 //
486 // todo: More efficient implementation
487 return getMemberChildren(member).length > 0;
488 } else {
489 // This is a regular level. It has children iff there is a lower
490 // level.
491 final Level childLevel = level.getChildLevel();
492 return (childLevel != null) &&
493 (role.getAccess(childLevel) != Access.NONE);
494 }
495 }
496
497 public boolean isVisible(Member member) {
498 return !member.isHidden() && role.canAccess(member);
499 }
500
501 public Cube[] getCubes() {
502 List<RolapCube> cubes = schema.getCubeList();
503 List<Cube> visibleCubes = new ArrayList<Cube>(cubes.size());
504
505 for (Cube cube : cubes) {
506 if (role.canAccess(cube)) {
507 visibleCubes.add(cube);
508 }
509 }
510
511 return visibleCubes.toArray(new Cube[visibleCubes.size()]);
512 }
513
514 public List<Member> getCalculatedMembers(Hierarchy hierarchy) {
515 return Collections.emptyList();
516 }
517
518 public List<Member> getCalculatedMembers(Level level) {
519 return Collections.emptyList();
520 }
521
522 public List<Member> getCalculatedMembers() {
523 return Collections.emptyList();
524 }
525
526 public NativeEvaluator getNativeSetEvaluator(
527 FunDef fun, Exp[] args, Evaluator evaluator, Calc calc) {
528 RolapEvaluator revaluator = (RolapEvaluator)
529 AbstractCalc.simplifyEvaluator(calc, evaluator);
530 return schema.getNativeRegistry().createEvaluator(revaluator, fun, args);
531 }
532
533 public Parameter getParameter(String name) {
534 // Scan through schema parameters.
535 for (RolapSchemaParameter parameter : schema.parameterList) {
536 if (Util.equalName(parameter.getName(), name)) {
537 return parameter;
538 }
539 }
540
541 // Scan through mondrian and system properties.
542 List<Property> propertyList = MondrianProperties.instance().getPropertyList();
543 for (Property property : propertyList) {
544 if (property.getPath().equals(name)) {
545 return new SystemPropertyParameter(name, false);
546 }
547 }
548 if (System.getProperty(name) != null) {
549 return new SystemPropertyParameter(name, true);
550 }
551
552 return null;
553 }
554
555 public DataSource getDataSource() {
556 return schema.getInternalConnection().getDataSource();
557 }
558
559 RolapSchema getSchema() {
560 return schema;
561 }
562
563 /**
564 * Implementation of {@link Parameter} which is sourced from system
565 * propertes (see {@link System#getProperties()} or mondrian properties
566 * (see {@link MondrianProperties}.
567 *
568 * <p>The name of the property is the same as the key into the
569 * {@link java.util.Properties} object; for example "java.version" or
570 * "mondrian.trace.level".
571 */
572 private static class SystemPropertyParameter
573 extends ParameterImpl
574 {
575 /**
576 * true if source is a system property;
577 * false if source is a mondrian property.
578 */
579 private final boolean system;
580 /**
581 * Definition of mondrian property, or null if system property.
582 */
583 private final Property propertyDefinition;
584
585 public SystemPropertyParameter(String name, boolean system) {
586 super(name,
587 Literal.nullValue,
588 "System property '" + name + "'",
589 new StringType());
590 this.system = system;
591 this.propertyDefinition =
592 system ? null :
593 MondrianProperties.instance().getPropertyDefinition(name);
594 }
595
596 public Scope getScope() {
597 return Scope.System;
598 }
599
600 public boolean isModifiable() {
601 return false;
602 }
603
604 public Calc compile(ExpCompiler compiler) {
605 return new GenericCalc(new DummyExp(getType())) {
606 public Calc[] getCalcs() {
607 return new Calc[0];
608 }
609
610 public Object evaluate(Evaluator evaluator) {
611 if (system) {
612 final String name = SystemPropertyParameter.this.getName();
613 return System.getProperty(name);
614 } else {
615 return propertyDefinition.stringValue();
616 }
617 }
618 };
619 }
620 }
621 }
622
623 // End RolapSchemaReader.java