PageRenderTime 29ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/src/main/mondrian/olap/Util.java

https://github.com/Berico-Technologies/mondrian
Java | 4488 lines | 2857 code | 333 blank | 1298 comment | 550 complexity | a95c531aeee77621ed993495fa86a3eb MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. // This software is subject to the terms of the Eclipse Public License v1.0
  3. // Agreement, available at the following URL:
  4. // http://www.eclipse.org/legal/epl-v10.html.
  5. // You must accept the terms of that agreement to use this software.
  6. //
  7. // Copyright (C) 2001-2005 Julian Hyde
  8. // Copyright (C) 2005-2013 Pentaho and others
  9. // All Rights Reserved.
  10. */
  11. package mondrian.olap;
  12. import mondrian.mdx.*;
  13. import mondrian.olap.fun.FunUtil;
  14. import mondrian.olap.fun.Resolver;
  15. import mondrian.olap.type.Type;
  16. import mondrian.resource.MondrianResource;
  17. import mondrian.rolap.*;
  18. import mondrian.spi.UserDefinedFunction;
  19. import mondrian.util.*;
  20. import org.apache.commons.collections.keyvalue.AbstractMapEntry;
  21. import org.apache.commons.vfs.*;
  22. import org.apache.commons.vfs.provider.http.HttpFileObject;
  23. import org.apache.log4j.Logger;
  24. import org.eigenbase.xom.XOMUtil;
  25. import org.olap4j.impl.Olap4jUtil;
  26. import org.olap4j.mdx.*;
  27. import java.io.*;
  28. import java.lang.ref.Reference;
  29. import java.lang.reflect.*;
  30. import java.lang.reflect.Array;
  31. import java.math.BigDecimal;
  32. import java.net.MalformedURLException;
  33. import java.net.URL;
  34. import java.security.MessageDigest;
  35. import java.security.NoSuchAlgorithmException;
  36. import java.sql.*;
  37. import java.sql.Connection;
  38. import java.util.*;
  39. import java.util.concurrent.*;
  40. import java.util.concurrent.atomic.AtomicInteger;
  41. import java.util.regex.Matcher;
  42. import java.util.regex.Pattern;
  43. /**
  44. * Utility functions used throughout mondrian. All methods are static.
  45. *
  46. * @author jhyde
  47. * @since 6 August, 2001
  48. */
  49. public class Util extends XOMUtil {
  50. public static final String nl = System.getProperty("line.separator");
  51. private static final Logger LOGGER = Logger.getLogger(Util.class);
  52. /**
  53. * Placeholder which indicates a value NULL.
  54. */
  55. public static final Object nullValue = new Double(FunUtil.DoubleNull);
  56. /**
  57. * Placeholder which indicates an EMPTY value.
  58. */
  59. public static final Object EmptyValue = new Double(FunUtil.DoubleEmpty);
  60. /**
  61. * Cumulative time spent accessing the database.
  62. */
  63. private static long databaseMillis = 0;
  64. /**
  65. * Random number generator to provide seed for other random number
  66. * generators.
  67. */
  68. private static final Random metaRandom =
  69. createRandom(MondrianProperties.instance().TestSeed.get());
  70. /** Unique id for this JVM instance. Part of a key that ensures that if
  71. * two JVMs in the same cluster have a data-source with the same
  72. * identity-hash-code, they will be treated as different data-sources,
  73. * and therefore caches will not be incorrectly shared. */
  74. public static final UUID JVM_INSTANCE_UUID = UUID.randomUUID();
  75. /**
  76. * Whether we are running a version of Java before 1.5.
  77. *
  78. * <p>If (but not only if) this variable is true, {@link #Retrowoven} will
  79. * also be true.
  80. */
  81. public static final boolean PreJdk15 =
  82. System.getProperty("java.version").startsWith("1.4");
  83. /**
  84. * Whether we are running a version of Java before 1.6.
  85. */
  86. public static final boolean PreJdk16 =
  87. PreJdk15
  88. || System.getProperty("java.version").startsWith("1.5");
  89. /**
  90. * Whether this is an IBM JVM.
  91. */
  92. public static final boolean IBM_JVM =
  93. System.getProperties().getProperty("java.vendor").equals(
  94. "IBM Corporation");
  95. /**
  96. * What version of JDBC?
  97. * Returns:<ul>
  98. * <li>0x0401 in JDK 1.7 and higher</li>
  99. * <li>0x0400 in JDK 1.6</li>
  100. * <li>0x0300 otherwise</li>
  101. * </ul>
  102. */
  103. public static final int JdbcVersion =
  104. System.getProperty("java.version").compareTo("1.7") >= 0
  105. ? 0x0401
  106. : System.getProperty("java.version").compareTo("1.6") >= 0
  107. ? 0x0400
  108. : 0x0300;
  109. /**
  110. * Whether the code base has re-engineered using retroweaver.
  111. * If this is the case, some functionality is not available, but a lot of
  112. * things are available via {@link mondrian.util.UtilCompatible}.
  113. * Retroweaver has some problems involving {@link java.util.EnumSet}.
  114. */
  115. public static final boolean Retrowoven =
  116. Access.class.getSuperclass().getName().equals(
  117. "net.sourceforge.retroweaver.runtime.java.lang.Enum");
  118. private static final UtilCompatible compatible;
  119. /**
  120. * Flag to control expensive debugging. (More expensive than merely
  121. * enabling assertions: as we know, a lot of people run with assertions
  122. * enabled.)
  123. */
  124. public static final boolean DEBUG = false;
  125. static {
  126. String className;
  127. if (PreJdk15 || Retrowoven) {
  128. className = "mondrian.util.UtilCompatibleJdk14";
  129. } else if (PreJdk16) {
  130. className = "mondrian.util.UtilCompatibleJdk15";
  131. } else {
  132. className = "mondrian.util.UtilCompatibleJdk16";
  133. }
  134. compatible = ClassResolver.INSTANCE.instantiateSafe(className);
  135. }
  136. public static boolean isNull(Object o) {
  137. return o == null || o == nullValue;
  138. }
  139. /**
  140. * Returns whether a list is strictly sorted.
  141. *
  142. * @param list List
  143. * @return whether list is sorted
  144. */
  145. public static <T> boolean isSorted(List<T> list) {
  146. T prev = null;
  147. for (T t : list) {
  148. if (prev != null
  149. && ((Comparable<T>) prev).compareTo(t) >= 0)
  150. {
  151. return false;
  152. }
  153. prev = t;
  154. }
  155. return true;
  156. }
  157. /**
  158. * Parses a string and returns a SHA-256 checksum of it.
  159. *
  160. * @param value The source string to parse.
  161. * @return A checksum of the source string.
  162. */
  163. public static byte[] digestSha256(String value) {
  164. final MessageDigest algorithm;
  165. try {
  166. algorithm = MessageDigest.getInstance("SHA-256");
  167. } catch (NoSuchAlgorithmException e) {
  168. throw new RuntimeException(e);
  169. }
  170. return algorithm.digest(value.getBytes());
  171. }
  172. /**
  173. * Creates an MD5 hash of a String.
  174. *
  175. * @param value String to create one way hash upon.
  176. * @return MD5 hash.
  177. */
  178. public static byte[] digestMd5(final String value) {
  179. final MessageDigest algorithm;
  180. try {
  181. algorithm = MessageDigest.getInstance("MD5");
  182. } catch (NoSuchAlgorithmException e) {
  183. throw new RuntimeException(e);
  184. }
  185. return algorithm.digest(value.getBytes());
  186. }
  187. /**
  188. * Creates an {@link ExecutorService} object backed by a thread pool.
  189. * @param maximumPoolSize Maximum number of concurrent
  190. * threads.
  191. * @param corePoolSize Minimum number of concurrent
  192. * threads to maintain in the pool, even if they are
  193. * idle.
  194. * @param keepAliveTime Time, in seconds, for which to
  195. * keep alive unused threads.
  196. * @param name The name of the threads.
  197. * @param rejectionPolicy The rejection policy to enforce.
  198. * @return An executor service preconfigured.
  199. */
  200. public static ExecutorService getExecutorService(
  201. int maximumPoolSize,
  202. int corePoolSize,
  203. long keepAliveTime,
  204. final String name,
  205. RejectedExecutionHandler rejectionPolicy)
  206. {
  207. if (Util.PreJdk16) {
  208. // On JDK1.5, if you specify corePoolSize=0, nothing gets executed.
  209. // Bummer.
  210. corePoolSize = Math.max(corePoolSize, 1);
  211. }
  212. // We must create a factory where the threads
  213. // have the right name and are marked as daemon threads.
  214. final ThreadFactory factory =
  215. new ThreadFactory() {
  216. private final AtomicInteger counter = new AtomicInteger(0);
  217. public Thread newThread(Runnable r) {
  218. final Thread t =
  219. Executors.defaultThreadFactory().newThread(r);
  220. t.setDaemon(true);
  221. t.setName(name + '_' + counter.incrementAndGet());
  222. return t;
  223. }
  224. };
  225. // Ok, create the executor
  226. final ThreadPoolExecutor executor =
  227. new ThreadPoolExecutor(
  228. corePoolSize,
  229. maximumPoolSize > 0
  230. ? maximumPoolSize
  231. : Integer.MAX_VALUE,
  232. keepAliveTime,
  233. TimeUnit.SECONDS,
  234. // we use a sync queue. any other type of queue
  235. // will prevent the tasks from running concurrently
  236. // because the executors API requires blocking queues.
  237. // Important to pass true here. This makes the
  238. // order of tasks deterministic.
  239. // TODO Write a non-blocking queue which implements
  240. // the blocking queue API so we can pass that to the
  241. // executor.
  242. new SynchronousQueue<Runnable>(true),
  243. factory);
  244. // Set the rejection policy if required.
  245. if (rejectionPolicy != null) {
  246. executor.setRejectedExecutionHandler(
  247. rejectionPolicy);
  248. }
  249. // Done
  250. return executor;
  251. }
  252. /**
  253. * Creates an {@link ScheduledExecutorService} object backed by a
  254. * thread pool with a fixed number of threads..
  255. * @param maxNbThreads Maximum number of concurrent
  256. * threads.
  257. * @param name The name of the threads.
  258. * @return An scheduled executor service preconfigured.
  259. */
  260. public static ScheduledExecutorService getScheduledExecutorService(
  261. final int maxNbThreads,
  262. final String name)
  263. {
  264. return Executors.newScheduledThreadPool(
  265. maxNbThreads,
  266. new ThreadFactory() {
  267. final AtomicInteger counter = new AtomicInteger(0);
  268. public Thread newThread(Runnable r) {
  269. final Thread thread =
  270. Executors.defaultThreadFactory().newThread(r);
  271. thread.setDaemon(true);
  272. thread.setName(name + '_' + counter.incrementAndGet());
  273. return thread;
  274. }
  275. }
  276. );
  277. }
  278. /**
  279. * Encodes string for MDX (escapes ] as ]] inside a name).
  280. *
  281. * @deprecated Will be removed in 4.0
  282. */
  283. public static String mdxEncodeString(String st) {
  284. StringBuilder retString = new StringBuilder(st.length() + 20);
  285. for (int i = 0; i < st.length(); i++) {
  286. char c = st.charAt(i);
  287. if ((c == ']')
  288. && ((i + 1) < st.length())
  289. && (st.charAt(i + 1) != '.'))
  290. {
  291. retString.append(']'); // escaping character
  292. }
  293. retString.append(c);
  294. }
  295. return retString.toString();
  296. }
  297. /**
  298. * Converts a string into a double-quoted string.
  299. */
  300. public static String quoteForMdx(String val) {
  301. StringBuilder buf = new StringBuilder(val.length() + 20);
  302. quoteForMdx(buf, val);
  303. return buf.toString();
  304. }
  305. /**
  306. * Appends a double-quoted string to a string builder.
  307. */
  308. public static StringBuilder quoteForMdx(StringBuilder buf, String val) {
  309. buf.append("\"");
  310. String s0 = replace(val, "\"", "\"\"");
  311. buf.append(s0);
  312. buf.append("\"");
  313. return buf;
  314. }
  315. /**
  316. * Return string quoted in [...]. For example, "San Francisco" becomes
  317. * "[San Francisco]"; "a [bracketed] string" becomes
  318. * "[a [bracketed]] string]".
  319. */
  320. public static String quoteMdxIdentifier(String id) {
  321. StringBuilder buf = new StringBuilder(id.length() + 20);
  322. quoteMdxIdentifier(id, buf);
  323. return buf.toString();
  324. }
  325. public static void quoteMdxIdentifier(String id, StringBuilder buf) {
  326. buf.append('[');
  327. int start = buf.length();
  328. buf.append(id);
  329. replace(buf, start, "]", "]]");
  330. buf.append(']');
  331. }
  332. /**
  333. * Return identifiers quoted in [...].[...]. For example, {"Store", "USA",
  334. * "California"} becomes "[Store].[USA].[California]".
  335. */
  336. public static String quoteMdxIdentifier(List<Id.Segment> ids) {
  337. StringBuilder sb = new StringBuilder(64);
  338. quoteMdxIdentifier(ids, sb);
  339. return sb.toString();
  340. }
  341. public static void quoteMdxIdentifier(
  342. List<Id.Segment> ids,
  343. StringBuilder sb)
  344. {
  345. for (int i = 0; i < ids.size(); i++) {
  346. if (i > 0) {
  347. sb.append('.');
  348. }
  349. ids.get(i).toString(sb);
  350. }
  351. }
  352. /**
  353. * Quotes a string literal for Java or JavaScript.
  354. *
  355. * @param s Unquoted literal
  356. * @return Quoted string literal
  357. */
  358. public static String quoteJavaString(String s) {
  359. return s == null
  360. ? "null"
  361. : "\""
  362. + s.replaceAll("\\\\", "\\\\\\\\")
  363. .replaceAll("\\\"", "\\\\\"")
  364. + "\"";
  365. }
  366. /**
  367. * Returns true if two objects are equal, or are both null.
  368. *
  369. * @param s First object
  370. * @param t Second object
  371. * @return Whether objects are equal or both null
  372. */
  373. public static boolean equals(Object s, Object t) {
  374. if (s == t) {
  375. return true;
  376. }
  377. if (s == null || t == null) {
  378. return false;
  379. }
  380. return s.equals(t);
  381. }
  382. /**
  383. * Returns true if two strings are equal, or are both null.
  384. *
  385. * <p>The result is not affected by
  386. * {@link MondrianProperties#CaseSensitive the case sensitive option}; if
  387. * you wish to compare names, use {@link #equalName(String, String)}.
  388. */
  389. public static boolean equals(String s, String t) {
  390. return equals((Object) s, (Object) t);
  391. }
  392. /**
  393. * Returns whether two names are equal.
  394. * Takes into account the
  395. * {@link MondrianProperties#CaseSensitive case sensitive option}.
  396. * Names may be null.
  397. */
  398. public static boolean equalName(String s, String t) {
  399. if (s == null) {
  400. return t == null;
  401. }
  402. boolean caseSensitive =
  403. MondrianProperties.instance().CaseSensitive.get();
  404. return caseSensitive ? s.equals(t) : s.equalsIgnoreCase(t);
  405. }
  406. /**
  407. * Tests two strings for equality, optionally ignoring case.
  408. *
  409. * @param s First string
  410. * @param t Second string
  411. * @param matchCase Whether to perform case-sensitive match
  412. * @return Whether strings are equal
  413. */
  414. public static boolean equal(String s, String t, boolean matchCase) {
  415. return matchCase ? s.equals(t) : s.equalsIgnoreCase(t);
  416. }
  417. /**
  418. * Compares two names. if case sensitive flag is false,
  419. * apply finer grain difference with case sensitive
  420. * Takes into account the {@link MondrianProperties#CaseSensitive case
  421. * sensitive option}.
  422. * Names must not be null.
  423. */
  424. public static int caseSensitiveCompareName(String s, String t) {
  425. boolean caseSensitive =
  426. MondrianProperties.instance().CaseSensitive.get();
  427. if (caseSensitive) {
  428. return s.compareTo(t);
  429. } else {
  430. int v = s.compareToIgnoreCase(t);
  431. // if ignore case returns 0 compare in a case sensitive manner
  432. // this was introduced to solve an issue with Member.equals()
  433. // and Member.compareTo() not agreeing with each other
  434. return v == 0 ? s.compareTo(t) : v;
  435. }
  436. }
  437. /**
  438. * Compares two names.
  439. * Takes into account the {@link MondrianProperties#CaseSensitive case
  440. * sensitive option}.
  441. * Names must not be null.
  442. */
  443. public static int compareName(String s, String t) {
  444. boolean caseSensitive =
  445. MondrianProperties.instance().CaseSensitive.get();
  446. return caseSensitive ? s.compareTo(t) : s.compareToIgnoreCase(t);
  447. }
  448. /**
  449. * Generates a normalized form of a name, for use as a key into a map.
  450. * Returns the upper case name if
  451. * {@link MondrianProperties#CaseSensitive} is true, the name unchanged
  452. * otherwise.
  453. */
  454. public static String normalizeName(String s) {
  455. return MondrianProperties.instance().CaseSensitive.get()
  456. ? s
  457. : s.toUpperCase();
  458. }
  459. /**
  460. * Returns the result of ((Comparable) k1).compareTo(k2), with
  461. * special-casing for the fact that Boolean only became
  462. * comparable in JDK 1.5.
  463. *
  464. * @see Comparable#compareTo
  465. */
  466. public static int compareKey(Object k1, Object k2) {
  467. if (k1 instanceof Boolean) {
  468. // Luckily, "F" comes before "T" in the alphabet.
  469. k1 = k1.toString();
  470. k2 = k2.toString();
  471. }
  472. return ((Comparable) k1).compareTo(k2);
  473. }
  474. /**
  475. * Compares integer values.
  476. *
  477. * @param i0 First integer
  478. * @param i1 Second integer
  479. * @return Comparison of integers
  480. */
  481. public static int compare(int i0, int i1) {
  482. return i0 < i1 ? -1 : (i0 == i1 ? 0 : 1);
  483. }
  484. /**
  485. * Returns a string with every occurrence of a seek string replaced with
  486. * another.
  487. */
  488. public static String replace(String s, String find, String replace) {
  489. // let's be optimistic
  490. int found = s.indexOf(find);
  491. if (found == -1) {
  492. return s;
  493. }
  494. StringBuilder sb = new StringBuilder(s.length() + 20);
  495. int start = 0;
  496. char[] chars = s.toCharArray();
  497. final int step = find.length();
  498. if (step == 0) {
  499. // Special case where find is "".
  500. sb.append(s);
  501. replace(sb, 0, find, replace);
  502. } else {
  503. for (;;) {
  504. sb.append(chars, start, found - start);
  505. if (found == s.length()) {
  506. break;
  507. }
  508. sb.append(replace);
  509. start = found + step;
  510. found = s.indexOf(find, start);
  511. if (found == -1) {
  512. found = s.length();
  513. }
  514. }
  515. }
  516. return sb.toString();
  517. }
  518. /**
  519. * Replaces all occurrences of a string in a buffer with another.
  520. *
  521. * @param buf String buffer to act on
  522. * @param start Ordinal within <code>find</code> to start searching
  523. * @param find String to find
  524. * @param replace String to replace it with
  525. * @return The string buffer
  526. */
  527. public static StringBuilder replace(
  528. StringBuilder buf,
  529. int start,
  530. String find,
  531. String replace)
  532. {
  533. // Search and replace from the end towards the start, to avoid O(n ^ 2)
  534. // copying if the string occurs very commonly.
  535. int findLength = find.length();
  536. if (findLength == 0) {
  537. // Special case where the seek string is empty.
  538. for (int j = buf.length(); j >= 0; --j) {
  539. buf.insert(j, replace);
  540. }
  541. return buf;
  542. }
  543. int k = buf.length();
  544. while (k > 0) {
  545. int i = buf.lastIndexOf(find, k);
  546. if (i < start) {
  547. break;
  548. }
  549. buf.replace(i, i + find.length(), replace);
  550. // Step back far enough to ensure that the beginning of the section
  551. // we just replaced does not cause a match.
  552. k = i - findLength;
  553. }
  554. return buf;
  555. }
  556. /**
  557. * Parses an MDX identifier such as <code>[Foo].[Bar].Baz.&Key&Key2</code>
  558. * and returns the result as a list of segments.
  559. *
  560. * @param s MDX identifier
  561. * @return List of segments
  562. */
  563. public static List<Id.Segment> parseIdentifier(String s) {
  564. return convert(
  565. org.olap4j.impl.IdentifierParser.parseIdentifier(s));
  566. }
  567. /**
  568. * Converts an array of name parts {"part1", "part2"} into a single string
  569. * "[part1].[part2]". If the names contain "]" they are escaped as "]]".
  570. */
  571. public static String implode(List<Id.Segment> names) {
  572. StringBuilder sb = new StringBuilder(64);
  573. for (int i = 0; i < names.size(); i++) {
  574. if (i > 0) {
  575. sb.append(".");
  576. }
  577. // FIXME: should be:
  578. // names.get(i).toString(sb);
  579. // but that causes some tests to fail
  580. Id.Segment segment = names.get(i);
  581. switch (segment.getQuoting()) {
  582. case UNQUOTED:
  583. segment = new Id.NameSegment(((Id.NameSegment) segment).name);
  584. }
  585. segment.toString(sb);
  586. }
  587. return sb.toString();
  588. }
  589. public static String makeFqName(String name) {
  590. return quoteMdxIdentifier(name);
  591. }
  592. public static String makeFqName(OlapElement parent, String name) {
  593. if (parent == null) {
  594. return Util.quoteMdxIdentifier(name);
  595. } else {
  596. StringBuilder buf = new StringBuilder(64);
  597. buf.append(parent.getUniqueName());
  598. buf.append('.');
  599. Util.quoteMdxIdentifier(name, buf);
  600. return buf.toString();
  601. }
  602. }
  603. public static String makeFqName(String parentUniqueName, String name) {
  604. if (parentUniqueName == null) {
  605. return quoteMdxIdentifier(name);
  606. } else {
  607. StringBuilder buf = new StringBuilder(64);
  608. buf.append(parentUniqueName);
  609. buf.append('.');
  610. Util.quoteMdxIdentifier(name, buf);
  611. return buf.toString();
  612. }
  613. }
  614. public static OlapElement lookupCompound(
  615. SchemaReader schemaReader,
  616. OlapElement parent,
  617. List<Id.Segment> names,
  618. boolean failIfNotFound,
  619. int category)
  620. {
  621. return lookupCompound(
  622. schemaReader, parent, names, failIfNotFound, category,
  623. MatchType.EXACT);
  624. }
  625. /**
  626. * Resolves a name such as
  627. * '[Products]&#46;[Product Department]&#46;[Produce]' by resolving the
  628. * components ('Products', and so forth) one at a time.
  629. *
  630. * @param schemaReader Schema reader, supplies access-control context
  631. * @param parent Parent element to search in
  632. * @param names Exploded compound name, such as {"Products",
  633. * "Product Department", "Produce"}
  634. * @param failIfNotFound If the element is not found, determines whether
  635. * to return null or throw an error
  636. * @param category Type of returned element, a {@link Category} value;
  637. * {@link Category#Unknown} if it doesn't matter.
  638. *
  639. * @pre parent != null
  640. * @post !(failIfNotFound && return == null)
  641. *
  642. * @see #parseIdentifier(String)
  643. */
  644. public static OlapElement lookupCompound(
  645. SchemaReader schemaReader,
  646. OlapElement parent,
  647. List<Id.Segment> names,
  648. boolean failIfNotFound,
  649. int category,
  650. MatchType matchType)
  651. {
  652. Util.assertPrecondition(parent != null, "parent != null");
  653. if (LOGGER.isDebugEnabled()) {
  654. StringBuilder buf = new StringBuilder(64);
  655. buf.append("Util.lookupCompound: ");
  656. buf.append("parent.name=");
  657. buf.append(parent.getName());
  658. buf.append(", category=");
  659. buf.append(Category.instance.getName(category));
  660. buf.append(", names=");
  661. quoteMdxIdentifier(names, buf);
  662. LOGGER.debug(buf.toString());
  663. }
  664. // First look up a member from the cache of calculated members
  665. // (cubes and queries both have them).
  666. switch (category) {
  667. case Category.Member:
  668. case Category.Unknown:
  669. Member member = schemaReader.getCalculatedMember(names);
  670. if (member != null) {
  671. return member;
  672. }
  673. }
  674. // Likewise named set.
  675. switch (category) {
  676. case Category.Set:
  677. case Category.Unknown:
  678. NamedSet namedSet = schemaReader.getNamedSet(names);
  679. if (namedSet != null) {
  680. return namedSet;
  681. }
  682. }
  683. // Now resolve the name one part at a time.
  684. for (int i = 0; i < names.size(); i++) {
  685. OlapElement child;
  686. Id.NameSegment name;
  687. if (names.get(i) instanceof Id.NameSegment) {
  688. name = (Id.NameSegment) names.get(i);
  689. child = schemaReader.getElementChild(parent, name, matchType);
  690. } else if (parent instanceof RolapLevel
  691. && names.get(i) instanceof Id.KeySegment
  692. && names.get(i).getKeyParts().size() == 1)
  693. {
  694. // The following code is for SsasCompatibleNaming=false.
  695. // Continues the very limited support for key segments in
  696. // mondrian-3.x. To be removed in mondrian-4, when
  697. // SsasCompatibleNaming=true is the only option.
  698. final Id.KeySegment keySegment = (Id.KeySegment) names.get(i);
  699. name = keySegment.getKeyParts().get(0);
  700. final List<Member> levelMembers =
  701. schemaReader.getLevelMembers(
  702. (Level) parent, false);
  703. child = null;
  704. for (Member member : levelMembers) {
  705. if (((RolapMember) member).getKey().toString().equals(
  706. name.getName()))
  707. {
  708. child = member;
  709. break;
  710. }
  711. }
  712. } else {
  713. name = null;
  714. child = schemaReader.getElementChild(parent, name, matchType);
  715. }
  716. // if we're doing a non-exact match, and we find a non-exact
  717. // match, then for an after match, return the first child
  718. // of each subsequent level; for a before match, return the
  719. // last child
  720. if (child instanceof Member
  721. && !matchType.isExact()
  722. && !Util.equalName(child.getName(), name.getName()))
  723. {
  724. Member bestChild = (Member) child;
  725. for (int j = i + 1; j < names.size(); j++) {
  726. List<Member> childrenList =
  727. schemaReader.getMemberChildren(bestChild);
  728. FunUtil.hierarchizeMemberList(childrenList, false);
  729. if (matchType == MatchType.AFTER) {
  730. bestChild = childrenList.get(0);
  731. } else {
  732. bestChild =
  733. childrenList.get(childrenList.size() - 1);
  734. }
  735. if (bestChild == null) {
  736. child = null;
  737. break;
  738. }
  739. }
  740. parent = bestChild;
  741. break;
  742. }
  743. if (child == null) {
  744. if (LOGGER.isDebugEnabled()) {
  745. LOGGER.debug(
  746. "Util.lookupCompound: "
  747. + "parent.name="
  748. + parent.getName()
  749. + " has no child with name="
  750. + name);
  751. }
  752. if (!failIfNotFound) {
  753. return null;
  754. } else if (category == Category.Member) {
  755. throw MondrianResource.instance().MemberNotFound.ex(
  756. quoteMdxIdentifier(names));
  757. } else {
  758. throw MondrianResource.instance().MdxChildObjectNotFound
  759. .ex(name.toString(), parent.getQualifiedName());
  760. }
  761. }
  762. parent = child;
  763. if (matchType == MatchType.EXACT_SCHEMA) {
  764. matchType = MatchType.EXACT;
  765. }
  766. }
  767. if (LOGGER.isDebugEnabled()) {
  768. LOGGER.debug(
  769. "Util.lookupCompound: "
  770. + "found child.name="
  771. + parent.getName()
  772. + ", child.class="
  773. + parent.getClass().getName());
  774. }
  775. switch (category) {
  776. case Category.Dimension:
  777. if (parent instanceof Dimension) {
  778. return parent;
  779. } else if (parent instanceof Hierarchy) {
  780. return parent.getDimension();
  781. } else if (failIfNotFound) {
  782. throw Util.newError(
  783. "Can not find dimension '" + implode(names) + "'");
  784. } else {
  785. return null;
  786. }
  787. case Category.Hierarchy:
  788. if (parent instanceof Hierarchy) {
  789. return parent;
  790. } else if (parent instanceof Dimension) {
  791. return parent.getHierarchy();
  792. } else if (failIfNotFound) {
  793. throw Util.newError(
  794. "Can not find hierarchy '" + implode(names) + "'");
  795. } else {
  796. return null;
  797. }
  798. case Category.Level:
  799. if (parent instanceof Level) {
  800. return parent;
  801. } else if (failIfNotFound) {
  802. throw Util.newError(
  803. "Can not find level '" + implode(names) + "'");
  804. } else {
  805. return null;
  806. }
  807. case Category.Member:
  808. if (parent instanceof Member) {
  809. return parent;
  810. } else if (failIfNotFound) {
  811. throw MondrianResource.instance().MdxCantFindMember.ex(
  812. implode(names));
  813. } else {
  814. return null;
  815. }
  816. case Category.Unknown:
  817. assertPostcondition(parent != null, "return != null");
  818. return parent;
  819. default:
  820. throw newInternal("Bad switch " + category);
  821. }
  822. }
  823. public static OlapElement lookup(Query q, List<Id.Segment> nameParts) {
  824. final Exp exp = lookup(q, nameParts, false);
  825. if (exp instanceof MemberExpr) {
  826. MemberExpr memberExpr = (MemberExpr) exp;
  827. return memberExpr.getMember();
  828. } else if (exp instanceof LevelExpr) {
  829. LevelExpr levelExpr = (LevelExpr) exp;
  830. return levelExpr.getLevel();
  831. } else if (exp instanceof HierarchyExpr) {
  832. HierarchyExpr hierarchyExpr = (HierarchyExpr) exp;
  833. return hierarchyExpr.getHierarchy();
  834. } else if (exp instanceof DimensionExpr) {
  835. DimensionExpr dimensionExpr = (DimensionExpr) exp;
  836. return dimensionExpr.getDimension();
  837. } else {
  838. throw Util.newInternal("Not an olap element: " + exp);
  839. }
  840. }
  841. /**
  842. * Converts an identifier into an expression by resolving its parts into
  843. * an OLAP object (dimension, hierarchy, level or member) within the
  844. * context of a query.
  845. *
  846. * <p>If <code>allowProp</code> is true, also allows property references
  847. * from valid members, for example
  848. * <code>[Measures].[Unit Sales].FORMATTED_VALUE</code>.
  849. * In this case, the result will be a {@link mondrian.mdx.ResolvedFunCall}.
  850. *
  851. * @param q Query expression belongs to
  852. * @param nameParts Parts of the identifier
  853. * @param allowProp Whether to allow property references
  854. * @return OLAP object or property reference
  855. */
  856. public static Exp lookup(
  857. Query q,
  858. List<Id.Segment> nameParts,
  859. boolean allowProp)
  860. {
  861. return lookup(q, q.getSchemaReader(true), nameParts, allowProp);
  862. }
  863. /**
  864. * Converts an identifier into an expression by resolving its parts into
  865. * an OLAP object (dimension, hierarchy, level or member) within the
  866. * context of a query.
  867. *
  868. * <p>If <code>allowProp</code> is true, also allows property references
  869. * from valid members, for example
  870. * <code>[Measures].[Unit Sales].FORMATTED_VALUE</code>.
  871. * In this case, the result will be a {@link ResolvedFunCall}.
  872. *
  873. * @param q Query expression belongs to
  874. * @param schemaReader Schema reader
  875. * @param segments Parts of the identifier
  876. * @param allowProp Whether to allow property references
  877. * @return OLAP object or property reference
  878. */
  879. public static Exp lookup(
  880. Query q,
  881. SchemaReader schemaReader,
  882. List<Id.Segment> segments,
  883. boolean allowProp)
  884. {
  885. // First, look for a calculated member defined in the query.
  886. final String fullName = quoteMdxIdentifier(segments);
  887. // Look for any kind of object (member, level, hierarchy,
  888. // dimension) in the cube. Use a schema reader without restrictions.
  889. final SchemaReader schemaReaderSansAc =
  890. schemaReader.withoutAccessControl().withLocus();
  891. final Cube cube = q.getCube();
  892. OlapElement olapElement =
  893. schemaReaderSansAc.lookupCompound(
  894. cube, segments, false, Category.Unknown);
  895. if (olapElement != null) {
  896. Role role = schemaReader.getRole();
  897. if (!role.canAccess(olapElement)) {
  898. olapElement = null;
  899. }
  900. if (olapElement instanceof Member) {
  901. olapElement =
  902. schemaReader.substitute((Member) olapElement);
  903. }
  904. }
  905. if (olapElement == null) {
  906. if (allowProp && segments.size() > 1) {
  907. List<Id.Segment> segmentsButOne =
  908. segments.subList(0, segments.size() - 1);
  909. final Id.Segment lastSegment = last(segments);
  910. final String propertyName =
  911. lastSegment instanceof Id.NameSegment
  912. ? ((Id.NameSegment) lastSegment).getName()
  913. : null;
  914. final Member member =
  915. (Member) schemaReaderSansAc.lookupCompound(
  916. cube, segmentsButOne, false, Category.Member);
  917. if (member != null
  918. && propertyName != null
  919. && isValidProperty(propertyName, member.getLevel()))
  920. {
  921. return new UnresolvedFunCall(
  922. propertyName, Syntax.Property, new Exp[] {
  923. createExpr(member)});
  924. }
  925. final Level level =
  926. (Level) schemaReaderSansAc.lookupCompound(
  927. cube, segmentsButOne, false, Category.Level);
  928. if (level != null
  929. && propertyName != null
  930. && isValidProperty(propertyName, level))
  931. {
  932. return new UnresolvedFunCall(
  933. propertyName, Syntax.Property, new Exp[] {
  934. createExpr(level)});
  935. }
  936. }
  937. // if we're in the middle of loading the schema, the property has
  938. // been set to ignore invalid members, and the member is
  939. // non-existent, return the null member corresponding to the
  940. // hierarchy of the element we're looking for; locate the
  941. // hierarchy by incrementally truncating the name of the element
  942. if (q.ignoreInvalidMembers()) {
  943. int nameLen = segments.size() - 1;
  944. olapElement = null;
  945. while (nameLen > 0 && olapElement == null) {
  946. List<Id.Segment> partialName =
  947. segments.subList(0, nameLen);
  948. olapElement = schemaReaderSansAc.lookupCompound(
  949. cube, partialName, false, Category.Unknown);
  950. nameLen--;
  951. }
  952. if (olapElement != null) {
  953. olapElement = olapElement.getHierarchy().getNullMember();
  954. } else {
  955. throw MondrianResource.instance().MdxChildObjectNotFound.ex(
  956. fullName, cube.getQualifiedName());
  957. }
  958. } else {
  959. throw MondrianResource.instance().MdxChildObjectNotFound.ex(
  960. fullName, cube.getQualifiedName());
  961. }
  962. }
  963. // keep track of any measure members referenced; these will be used
  964. // later to determine if cross joins on virtual cubes can be
  965. // processed natively
  966. q.addMeasuresMembers(olapElement);
  967. return createExpr(olapElement);
  968. }
  969. /**
  970. * Looks up a cube in a schema reader.
  971. *
  972. * @param cubeName Cube name
  973. * @param fail Whether to fail if not found.
  974. * @return Cube, or null if not found
  975. */
  976. static Cube lookupCube(
  977. SchemaReader schemaReader,
  978. String cubeName,
  979. boolean fail)
  980. {
  981. for (Cube cube : schemaReader.getCubes()) {
  982. if (Util.compareName(cube.getName(), cubeName) == 0) {
  983. return cube;
  984. }
  985. }
  986. if (fail) {
  987. throw MondrianResource.instance().MdxCubeNotFound.ex(cubeName);
  988. }
  989. return null;
  990. }
  991. /**
  992. * Converts an olap element (dimension, hierarchy, level or member) into
  993. * an expression representing a usage of that element in an MDX statement.
  994. */
  995. public static Exp createExpr(OlapElement element)
  996. {
  997. if (element instanceof Member) {
  998. Member member = (Member) element;
  999. return new MemberExpr(member);
  1000. } else if (element instanceof Level) {
  1001. Level level = (Level) element;
  1002. return new LevelExpr(level);
  1003. } else if (element instanceof Hierarchy) {
  1004. Hierarchy hierarchy = (Hierarchy) element;
  1005. return new HierarchyExpr(hierarchy);
  1006. } else if (element instanceof Dimension) {
  1007. Dimension dimension = (Dimension) element;
  1008. return new DimensionExpr(dimension);
  1009. } else if (element instanceof NamedSet) {
  1010. NamedSet namedSet = (NamedSet) element;
  1011. return new NamedSetExpr(namedSet);
  1012. } else {
  1013. throw Util.newInternal("Unexpected element type: " + element);
  1014. }
  1015. }
  1016. public static Member lookupHierarchyRootMember(
  1017. SchemaReader reader, Hierarchy hierarchy, Id.NameSegment memberName)
  1018. {
  1019. return lookupHierarchyRootMember(
  1020. reader, hierarchy, memberName, MatchType.EXACT);
  1021. }
  1022. /**
  1023. * Finds a root member of a hierarchy with a given name.
  1024. *
  1025. * @param hierarchy Hierarchy
  1026. * @param memberName Name of root member
  1027. * @return Member, or null if not found
  1028. */
  1029. public static Member lookupHierarchyRootMember(
  1030. SchemaReader reader,
  1031. Hierarchy hierarchy,
  1032. Id.NameSegment memberName,
  1033. MatchType matchType)
  1034. {
  1035. // Lookup member at first level.
  1036. //
  1037. // Don't use access control. Suppose we cannot see the 'nation' level,
  1038. // we still want to be able to resolve '[Customer].[USA].[CA]'.
  1039. List<Member> rootMembers = reader.getHierarchyRootMembers(hierarchy);
  1040. // if doing an inexact search on a non-all hierarchy, create
  1041. // a member corresponding to the name we're searching for so
  1042. // we can use it in a hierarchical search
  1043. Member searchMember = null;
  1044. if (!matchType.isExact()
  1045. && !hierarchy.hasAll()
  1046. && !rootMembers.isEmpty())
  1047. {
  1048. searchMember =
  1049. hierarchy.createMember(
  1050. null,
  1051. rootMembers.get(0).getLevel(),
  1052. memberName.name,
  1053. null);
  1054. }
  1055. int bestMatch = -1;
  1056. int k = -1;
  1057. for (Member rootMember : rootMembers) {
  1058. ++k;
  1059. int rc;
  1060. // when searching on the ALL hierarchy, match must be exact
  1061. if (matchType.isExact() || hierarchy.hasAll()) {
  1062. rc = rootMember.getName().compareToIgnoreCase(memberName.name);
  1063. } else {
  1064. rc = FunUtil.compareSiblingMembers(
  1065. rootMember,
  1066. searchMember);
  1067. }
  1068. if (rc == 0) {
  1069. return rootMember;
  1070. }
  1071. if (!hierarchy.hasAll()) {
  1072. if (matchType == MatchType.BEFORE) {
  1073. if (rc < 0
  1074. && (bestMatch == -1
  1075. || FunUtil.compareSiblingMembers(
  1076. rootMember,
  1077. rootMembers.get(bestMatch)) > 0))
  1078. {
  1079. bestMatch = k;
  1080. }
  1081. } else if (matchType == MatchType.AFTER) {
  1082. if (rc > 0
  1083. && (bestMatch == -1
  1084. || FunUtil.compareSiblingMembers(
  1085. rootMember,
  1086. rootMembers.get(bestMatch)) < 0))
  1087. {
  1088. bestMatch = k;
  1089. }
  1090. }
  1091. }
  1092. }
  1093. if (matchType == MatchType.EXACT_SCHEMA) {
  1094. return null;
  1095. }
  1096. if (matchType != MatchType.EXACT && bestMatch != -1) {
  1097. return rootMembers.get(bestMatch);
  1098. }
  1099. // If the first level is 'all', lookup member at second level. For
  1100. // example, they could say '[USA]' instead of '[(All
  1101. // Customers)].[USA]'.
  1102. return (rootMembers.size() > 0 && rootMembers.get(0).isAll())
  1103. ? reader.lookupMemberChildByName(
  1104. rootMembers.get(0),
  1105. memberName,
  1106. matchType)
  1107. : null;
  1108. }
  1109. /**
  1110. * Finds a named level in this hierarchy. Returns null if there is no
  1111. * such level.
  1112. */
  1113. public static Level lookupHierarchyLevel(Hierarchy hierarchy, String s) {
  1114. final Level[] levels = hierarchy.getLevels();
  1115. for (Level level : levels) {
  1116. if (level.getName().equalsIgnoreCase(s)) {
  1117. return level;
  1118. }
  1119. }
  1120. return null;
  1121. }
  1122. /**
  1123. * Finds the zero based ordinal of a Member among its siblings.
  1124. */
  1125. public static int getMemberOrdinalInParent(
  1126. SchemaReader reader,
  1127. Member member)
  1128. {
  1129. Member parent = member.getParentMember();
  1130. List<Member> siblings =
  1131. (parent == null)
  1132. ? reader.getHierarchyRootMembers(member.getHierarchy())
  1133. : reader.getMemberChildren(parent);
  1134. for (int i = 0; i < siblings.size(); i++) {
  1135. if (siblings.get(i).equals(member)) {
  1136. return i;
  1137. }
  1138. }
  1139. throw Util.newInternal(
  1140. "could not find member " + member + " amongst its siblings");
  1141. }
  1142. /**
  1143. * returns the first descendant on the level underneath parent.
  1144. * If parent = [Time].[1997] and level = [Time].[Month], then
  1145. * the member [Time].[1997].[Q1].[1] will be returned
  1146. */
  1147. public static Member getFirstDescendantOnLevel(
  1148. SchemaReader reader,
  1149. Member parent,
  1150. Level level)
  1151. {
  1152. Member m = parent;
  1153. while (m.getLevel() != level) {
  1154. List<Member> children = reader.getMemberChildren(m);
  1155. m = children.get(0);
  1156. }
  1157. return m;
  1158. }
  1159. /**
  1160. * Returns whether a string is null or empty.
  1161. */
  1162. public static boolean isEmpty(String s) {
  1163. return (s == null) || (s.length() == 0);
  1164. }
  1165. /**
  1166. * Encloses a value in single-quotes, to make a SQL string value. Examples:
  1167. * <code>singleQuoteForSql(null)</code> yields <code>NULL</code>;
  1168. * <code>singleQuoteForSql("don't")</code> yields <code>'don''t'</code>.
  1169. */
  1170. public static String singleQuoteString(String val) {
  1171. StringBuilder buf = new StringBuilder(64);
  1172. singleQuoteString(val, buf);
  1173. return buf.toString();
  1174. }
  1175. /**
  1176. * Encloses a value in single-quotes, to make a SQL string value. Examples:
  1177. * <code>singleQuoteForSql(null)</code> yields <code>NULL</code>;
  1178. * <code>singleQuoteForSql("don't")</code> yields <code>'don''t'</code>.
  1179. */
  1180. public static void singleQuoteString(String val, StringBuilder buf) {
  1181. buf.append('\'');
  1182. String s0 = replace(val, "'", "''");
  1183. buf.append(s0);
  1184. buf.append('\'');
  1185. }
  1186. /**
  1187. * Creates a random number generator.
  1188. *
  1189. * @param seed Seed for random number generator.
  1190. * If 0, generate a seed from the system clock and print the value
  1191. * chosen. (This is effectively non-deterministic.)
  1192. * If -1, generate a seed from an internal random number generator.
  1193. * (This is deterministic, but ensures that different tests have
  1194. * different seeds.)
  1195. *
  1196. * @return A random number generator.
  1197. */
  1198. public static Random createRandom(long seed) {
  1199. if (seed == 0) {
  1200. seed = new Random().nextLong();
  1201. System.out.println("random: seed=" + seed);
  1202. } else if (seed == -1 && metaRandom != null) {
  1203. seed = metaRandom.nextLong();
  1204. }
  1205. return new Random(seed);
  1206. }
  1207. /**
  1208. * Returns whether a property is valid for a member of a given level.
  1209. * It is valid if the property is defined at the level or at
  1210. * an ancestor level, or if the property is a standard property such as
  1211. * "FORMATTED_VALUE".
  1212. *
  1213. * @param propertyName Property name
  1214. * @param level Level
  1215. * @return Whether property is valid
  1216. */
  1217. public static boolean isValidProperty(
  1218. String propertyName,
  1219. Level level)
  1220. {
  1221. return lookupProperty(level, propertyName) != null;
  1222. }
  1223. /**
  1224. * Finds a member property called <code>propertyName</code> at, or above,
  1225. * <code>level</code>.
  1226. */
  1227. public static Property lookupProperty(
  1228. Level level,
  1229. String propertyName)
  1230. {
  1231. do {
  1232. Property[] properties = level.getProperties();
  1233. for (Property property : properties) {
  1234. if (property.getName().equals(propertyName)) {
  1235. return property;
  1236. }
  1237. }
  1238. level = level.getParentLevel();
  1239. } while (level != null);
  1240. // Now try a standard property.
  1241. boolean caseSensitive =
  1242. MondrianProperties.instance().CaseSensitive.get();
  1243. final Property property = Property.lookup(propertyName, caseSensitive);
  1244. if (property != null
  1245. && property.isMemberProperty()
  1246. && property.isStandard())
  1247. {
  1248. return property;
  1249. }
  1250. return null;
  1251. }
  1252. /**
  1253. * Insert a call to this method if you want to flag a piece of
  1254. * undesirable code.
  1255. *
  1256. * @deprecated
  1257. */
  1258. public static <T> T deprecated(T reason) {
  1259. throw new UnsupportedOperationException(reason.toString());
  1260. }
  1261. /**
  1262. * Insert a call to this method if you want to flag a piece of
  1263. * undesirable code.
  1264. *
  1265. * @deprecated
  1266. */
  1267. public static <T> T deprecated(T reason, boolean fail) {
  1268. if (fail) {
  1269. throw new UnsupportedOperationException(reason.toString());
  1270. } else {
  1271. return reason;
  1272. }
  1273. }
  1274. public static List<Member> addLevelCalculatedMembers(
  1275. SchemaReader reader,
  1276. Level level,
  1277. List<Member> members)
  1278. {
  1279. List<Member> calcMembers =
  1280. reader.getCalculatedMembers(level.getHierarchy());
  1281. List<Member> calcMembersInThisLevel = new ArrayList<Member>();
  1282. for (Member calcMember : calcMembers) {
  1283. if (calcMember.getLevel().equals(level)) {
  1284. calcMembersInThisLevel.add(calcMember);
  1285. }
  1286. }
  1287. if (!calcMembersInThisLevel.isEmpty()) {
  1288. List<Member> newMemberList =
  1289. new ConcatenableList<Member>();
  1290. newMemberList.addAll(members);
  1291. newMemberList.addAll(calcMembersInThisLevel);
  1292. return newMemberList;
  1293. }
  1294. return members;
  1295. }
  1296. /**
  1297. * Returns an exception which indicates that a particular piece of
  1298. * functionality should work, but a developer has not implemented it yet.
  1299. */
  1300. public static RuntimeException needToImplement(Object o) {
  1301. throw new UnsupportedOperationException("need to implement " + o);
  1302. }
  1303. /**
  1304. * Returns an exception indicating that we didn't expect to find this value
  1305. * here.
  1306. */
  1307. public static <T extends Enum<T>> RuntimeException badValue(
  1308. Enum<T> anEnum)
  1309. {
  1310. return Util.newInternal(
  1311. "Was not expecting value '" + anEnum
  1312. + "' for enumeration '" + anEnum.getDeclaringClass().getName()
  1313. + "' in this context");
  1314. }
  1315. /**
  1316. * Masks Mondrian's version number from a string.
  1317. *
  1318. * @param str String
  1319. * @return String with each occurrence of mondrian's version number
  1320. * (e.g. "2.3.0.0") replaced with "${mondrianVersion}"
  1321. */
  1322. public static String maskVersion(String str) {
  1323. MondrianServer.MondrianVersion mondrianVersion =
  1324. MondrianServer.forId(null).getVersion();
  1325. String versionString = mondrianVersion.getVersionString();
  1326. return replace(str, versionString, "${mondrianVersion}");
  1327. }
  1328. /**
  1329. * Converts a list of SQL-style patterns into a Java regular expression.
  1330. *
  1331. * <p>For example, {"Foo_", "Bar%BAZ"} becomes "Foo.|Bar.*BAZ".
  1332. *
  1333. * @param wildcards List of SQL-style wildcard expressions
  1334. * @return Regular expression
  1335. */
  1336. public static String wildcardToRegexp(List<String> wildcards) {
  1337. StringBuilder buf = new StringBuilder();
  1338. for (String value : wildcards) {
  1339. if (buf.length() > 0) {
  1340. buf.append('|');
  1341. }
  1342. int i = 0;
  1343. while (true) {
  1344. int percent = value.indexOf('%', i);
  1345. int underscore = value.indexOf('_', i);
  1346. if (percent == -1 && underscore == -1) {
  1347. if (i < value.length()) {

Large files files are truncated, but you can click here to view the full file