/src/main/mondrian/olap/Util.java
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
- /*
- // This software is subject to the terms of the Eclipse Public License v1.0
- // Agreement, available at the following URL:
- // http://www.eclipse.org/legal/epl-v10.html.
- // You must accept the terms of that agreement to use this software.
- //
- // Copyright (C) 2001-2005 Julian Hyde
- // Copyright (C) 2005-2013 Pentaho and others
- // All Rights Reserved.
- */
- package mondrian.olap;
- import mondrian.mdx.*;
- import mondrian.olap.fun.FunUtil;
- import mondrian.olap.fun.Resolver;
- import mondrian.olap.type.Type;
- import mondrian.resource.MondrianResource;
- import mondrian.rolap.*;
- import mondrian.spi.UserDefinedFunction;
- import mondrian.util.*;
- import org.apache.commons.collections.keyvalue.AbstractMapEntry;
- import org.apache.commons.vfs.*;
- import org.apache.commons.vfs.provider.http.HttpFileObject;
- import org.apache.log4j.Logger;
- import org.eigenbase.xom.XOMUtil;
- import org.olap4j.impl.Olap4jUtil;
- import org.olap4j.mdx.*;
- import java.io.*;
- import java.lang.ref.Reference;
- import java.lang.reflect.*;
- import java.lang.reflect.Array;
- import java.math.BigDecimal;
- import java.net.MalformedURLException;
- import java.net.URL;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import java.sql.*;
- import java.sql.Connection;
- import java.util.*;
- import java.util.concurrent.*;
- import java.util.concurrent.atomic.AtomicInteger;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- /**
- * Utility functions used throughout mondrian. All methods are static.
- *
- * @author jhyde
- * @since 6 August, 2001
- */
- public class Util extends XOMUtil {
- public static final String nl = System.getProperty("line.separator");
- private static final Logger LOGGER = Logger.getLogger(Util.class);
- /**
- * Placeholder which indicates a value NULL.
- */
- public static final Object nullValue = new Double(FunUtil.DoubleNull);
- /**
- * Placeholder which indicates an EMPTY value.
- */
- public static final Object EmptyValue = new Double(FunUtil.DoubleEmpty);
- /**
- * Cumulative time spent accessing the database.
- */
- private static long databaseMillis = 0;
- /**
- * Random number generator to provide seed for other random number
- * generators.
- */
- private static final Random metaRandom =
- createRandom(MondrianProperties.instance().TestSeed.get());
- /** Unique id for this JVM instance. Part of a key that ensures that if
- * two JVMs in the same cluster have a data-source with the same
- * identity-hash-code, they will be treated as different data-sources,
- * and therefore caches will not be incorrectly shared. */
- public static final UUID JVM_INSTANCE_UUID = UUID.randomUUID();
- /**
- * Whether we are running a version of Java before 1.5.
- *
- * <p>If (but not only if) this variable is true, {@link #Retrowoven} will
- * also be true.
- */
- public static final boolean PreJdk15 =
- System.getProperty("java.version").startsWith("1.4");
- /**
- * Whether we are running a version of Java before 1.6.
- */
- public static final boolean PreJdk16 =
- PreJdk15
- || System.getProperty("java.version").startsWith("1.5");
- /**
- * Whether this is an IBM JVM.
- */
- public static final boolean IBM_JVM =
- System.getProperties().getProperty("java.vendor").equals(
- "IBM Corporation");
- /**
- * What version of JDBC?
- * Returns:<ul>
- * <li>0x0401 in JDK 1.7 and higher</li>
- * <li>0x0400 in JDK 1.6</li>
- * <li>0x0300 otherwise</li>
- * </ul>
- */
- public static final int JdbcVersion =
- System.getProperty("java.version").compareTo("1.7") >= 0
- ? 0x0401
- : System.getProperty("java.version").compareTo("1.6") >= 0
- ? 0x0400
- : 0x0300;
- /**
- * Whether the code base has re-engineered using retroweaver.
- * If this is the case, some functionality is not available, but a lot of
- * things are available via {@link mondrian.util.UtilCompatible}.
- * Retroweaver has some problems involving {@link java.util.EnumSet}.
- */
- public static final boolean Retrowoven =
- Access.class.getSuperclass().getName().equals(
- "net.sourceforge.retroweaver.runtime.java.lang.Enum");
- private static final UtilCompatible compatible;
- /**
- * Flag to control expensive debugging. (More expensive than merely
- * enabling assertions: as we know, a lot of people run with assertions
- * enabled.)
- */
- public static final boolean DEBUG = false;
- static {
- String className;
- if (PreJdk15 || Retrowoven) {
- className = "mondrian.util.UtilCompatibleJdk14";
- } else if (PreJdk16) {
- className = "mondrian.util.UtilCompatibleJdk15";
- } else {
- className = "mondrian.util.UtilCompatibleJdk16";
- }
- compatible = ClassResolver.INSTANCE.instantiateSafe(className);
- }
- public static boolean isNull(Object o) {
- return o == null || o == nullValue;
- }
- /**
- * Returns whether a list is strictly sorted.
- *
- * @param list List
- * @return whether list is sorted
- */
- public static <T> boolean isSorted(List<T> list) {
- T prev = null;
- for (T t : list) {
- if (prev != null
- && ((Comparable<T>) prev).compareTo(t) >= 0)
- {
- return false;
- }
- prev = t;
- }
- return true;
- }
- /**
- * Parses a string and returns a SHA-256 checksum of it.
- *
- * @param value The source string to parse.
- * @return A checksum of the source string.
- */
- public static byte[] digestSha256(String value) {
- final MessageDigest algorithm;
- try {
- algorithm = MessageDigest.getInstance("SHA-256");
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- return algorithm.digest(value.getBytes());
- }
- /**
- * Creates an MD5 hash of a String.
- *
- * @param value String to create one way hash upon.
- * @return MD5 hash.
- */
- public static byte[] digestMd5(final String value) {
- final MessageDigest algorithm;
- try {
- algorithm = MessageDigest.getInstance("MD5");
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- return algorithm.digest(value.getBytes());
- }
- /**
- * Creates an {@link ExecutorService} object backed by a thread pool.
- * @param maximumPoolSize Maximum number of concurrent
- * threads.
- * @param corePoolSize Minimum number of concurrent
- * threads to maintain in the pool, even if they are
- * idle.
- * @param keepAliveTime Time, in seconds, for which to
- * keep alive unused threads.
- * @param name The name of the threads.
- * @param rejectionPolicy The rejection policy to enforce.
- * @return An executor service preconfigured.
- */
- public static ExecutorService getExecutorService(
- int maximumPoolSize,
- int corePoolSize,
- long keepAliveTime,
- final String name,
- RejectedExecutionHandler rejectionPolicy)
- {
- if (Util.PreJdk16) {
- // On JDK1.5, if you specify corePoolSize=0, nothing gets executed.
- // Bummer.
- corePoolSize = Math.max(corePoolSize, 1);
- }
- // We must create a factory where the threads
- // have the right name and are marked as daemon threads.
- final ThreadFactory factory =
- new ThreadFactory() {
- private final AtomicInteger counter = new AtomicInteger(0);
- public Thread newThread(Runnable r) {
- final Thread t =
- Executors.defaultThreadFactory().newThread(r);
- t.setDaemon(true);
- t.setName(name + '_' + counter.incrementAndGet());
- return t;
- }
- };
- // Ok, create the executor
- final ThreadPoolExecutor executor =
- new ThreadPoolExecutor(
- corePoolSize,
- maximumPoolSize > 0
- ? maximumPoolSize
- : Integer.MAX_VALUE,
- keepAliveTime,
- TimeUnit.SECONDS,
- // we use a sync queue. any other type of queue
- // will prevent the tasks from running concurrently
- // because the executors API requires blocking queues.
- // Important to pass true here. This makes the
- // order of tasks deterministic.
- // TODO Write a non-blocking queue which implements
- // the blocking queue API so we can pass that to the
- // executor.
- new SynchronousQueue<Runnable>(true),
- factory);
- // Set the rejection policy if required.
- if (rejectionPolicy != null) {
- executor.setRejectedExecutionHandler(
- rejectionPolicy);
- }
- // Done
- return executor;
- }
- /**
- * Creates an {@link ScheduledExecutorService} object backed by a
- * thread pool with a fixed number of threads..
- * @param maxNbThreads Maximum number of concurrent
- * threads.
- * @param name The name of the threads.
- * @return An scheduled executor service preconfigured.
- */
- public static ScheduledExecutorService getScheduledExecutorService(
- final int maxNbThreads,
- final String name)
- {
- return Executors.newScheduledThreadPool(
- maxNbThreads,
- new ThreadFactory() {
- final AtomicInteger counter = new AtomicInteger(0);
- public Thread newThread(Runnable r) {
- final Thread thread =
- Executors.defaultThreadFactory().newThread(r);
- thread.setDaemon(true);
- thread.setName(name + '_' + counter.incrementAndGet());
- return thread;
- }
- }
- );
- }
- /**
- * Encodes string for MDX (escapes ] as ]] inside a name).
- *
- * @deprecated Will be removed in 4.0
- */
- public static String mdxEncodeString(String st) {
- StringBuilder retString = new StringBuilder(st.length() + 20);
- for (int i = 0; i < st.length(); i++) {
- char c = st.charAt(i);
- if ((c == ']')
- && ((i + 1) < st.length())
- && (st.charAt(i + 1) != '.'))
- {
- retString.append(']'); // escaping character
- }
- retString.append(c);
- }
- return retString.toString();
- }
- /**
- * Converts a string into a double-quoted string.
- */
- public static String quoteForMdx(String val) {
- StringBuilder buf = new StringBuilder(val.length() + 20);
- quoteForMdx(buf, val);
- return buf.toString();
- }
- /**
- * Appends a double-quoted string to a string builder.
- */
- public static StringBuilder quoteForMdx(StringBuilder buf, String val) {
- buf.append("\"");
- String s0 = replace(val, "\"", "\"\"");
- buf.append(s0);
- buf.append("\"");
- return buf;
- }
- /**
- * Return string quoted in [...]. For example, "San Francisco" becomes
- * "[San Francisco]"; "a [bracketed] string" becomes
- * "[a [bracketed]] string]".
- */
- public static String quoteMdxIdentifier(String id) {
- StringBuilder buf = new StringBuilder(id.length() + 20);
- quoteMdxIdentifier(id, buf);
- return buf.toString();
- }
- public static void quoteMdxIdentifier(String id, StringBuilder buf) {
- buf.append('[');
- int start = buf.length();
- buf.append(id);
- replace(buf, start, "]", "]]");
- buf.append(']');
- }
- /**
- * Return identifiers quoted in [...].[...]. For example, {"Store", "USA",
- * "California"} becomes "[Store].[USA].[California]".
- */
- public static String quoteMdxIdentifier(List<Id.Segment> ids) {
- StringBuilder sb = new StringBuilder(64);
- quoteMdxIdentifier(ids, sb);
- return sb.toString();
- }
- public static void quoteMdxIdentifier(
- List<Id.Segment> ids,
- StringBuilder sb)
- {
- for (int i = 0; i < ids.size(); i++) {
- if (i > 0) {
- sb.append('.');
- }
- ids.get(i).toString(sb);
- }
- }
- /**
- * Quotes a string literal for Java or JavaScript.
- *
- * @param s Unquoted literal
- * @return Quoted string literal
- */
- public static String quoteJavaString(String s) {
- return s == null
- ? "null"
- : "\""
- + s.replaceAll("\\\\", "\\\\\\\\")
- .replaceAll("\\\"", "\\\\\"")
- + "\"";
- }
- /**
- * Returns true if two objects are equal, or are both null.
- *
- * @param s First object
- * @param t Second object
- * @return Whether objects are equal or both null
- */
- public static boolean equals(Object s, Object t) {
- if (s == t) {
- return true;
- }
- if (s == null || t == null) {
- return false;
- }
- return s.equals(t);
- }
- /**
- * Returns true if two strings are equal, or are both null.
- *
- * <p>The result is not affected by
- * {@link MondrianProperties#CaseSensitive the case sensitive option}; if
- * you wish to compare names, use {@link #equalName(String, String)}.
- */
- public static boolean equals(String s, String t) {
- return equals((Object) s, (Object) t);
- }
- /**
- * Returns whether two names are equal.
- * Takes into account the
- * {@link MondrianProperties#CaseSensitive case sensitive option}.
- * Names may be null.
- */
- public static boolean equalName(String s, String t) {
- if (s == null) {
- return t == null;
- }
- boolean caseSensitive =
- MondrianProperties.instance().CaseSensitive.get();
- return caseSensitive ? s.equals(t) : s.equalsIgnoreCase(t);
- }
- /**
- * Tests two strings for equality, optionally ignoring case.
- *
- * @param s First string
- * @param t Second string
- * @param matchCase Whether to perform case-sensitive match
- * @return Whether strings are equal
- */
- public static boolean equal(String s, String t, boolean matchCase) {
- return matchCase ? s.equals(t) : s.equalsIgnoreCase(t);
- }
- /**
- * Compares two names. if case sensitive flag is false,
- * apply finer grain difference with case sensitive
- * Takes into account the {@link MondrianProperties#CaseSensitive case
- * sensitive option}.
- * Names must not be null.
- */
- public static int caseSensitiveCompareName(String s, String t) {
- boolean caseSensitive =
- MondrianProperties.instance().CaseSensitive.get();
- if (caseSensitive) {
- return s.compareTo(t);
- } else {
- int v = s.compareToIgnoreCase(t);
- // if ignore case returns 0 compare in a case sensitive manner
- // this was introduced to solve an issue with Member.equals()
- // and Member.compareTo() not agreeing with each other
- return v == 0 ? s.compareTo(t) : v;
- }
- }
- /**
- * Compares two names.
- * Takes into account the {@link MondrianProperties#CaseSensitive case
- * sensitive option}.
- * Names must not be null.
- */
- public static int compareName(String s, String t) {
- boolean caseSensitive =
- MondrianProperties.instance().CaseSensitive.get();
- return caseSensitive ? s.compareTo(t) : s.compareToIgnoreCase(t);
- }
- /**
- * Generates a normalized form of a name, for use as a key into a map.
- * Returns the upper case name if
- * {@link MondrianProperties#CaseSensitive} is true, the name unchanged
- * otherwise.
- */
- public static String normalizeName(String s) {
- return MondrianProperties.instance().CaseSensitive.get()
- ? s
- : s.toUpperCase();
- }
- /**
- * Returns the result of ((Comparable) k1).compareTo(k2), with
- * special-casing for the fact that Boolean only became
- * comparable in JDK 1.5.
- *
- * @see Comparable#compareTo
- */
- public static int compareKey(Object k1, Object k2) {
- if (k1 instanceof Boolean) {
- // Luckily, "F" comes before "T" in the alphabet.
- k1 = k1.toString();
- k2 = k2.toString();
- }
- return ((Comparable) k1).compareTo(k2);
- }
- /**
- * Compares integer values.
- *
- * @param i0 First integer
- * @param i1 Second integer
- * @return Comparison of integers
- */
- public static int compare(int i0, int i1) {
- return i0 < i1 ? -1 : (i0 == i1 ? 0 : 1);
- }
- /**
- * Returns a string with every occurrence of a seek string replaced with
- * another.
- */
- public static String replace(String s, String find, String replace) {
- // let's be optimistic
- int found = s.indexOf(find);
- if (found == -1) {
- return s;
- }
- StringBuilder sb = new StringBuilder(s.length() + 20);
- int start = 0;
- char[] chars = s.toCharArray();
- final int step = find.length();
- if (step == 0) {
- // Special case where find is "".
- sb.append(s);
- replace(sb, 0, find, replace);
- } else {
- for (;;) {
- sb.append(chars, start, found - start);
- if (found == s.length()) {
- break;
- }
- sb.append(replace);
- start = found + step;
- found = s.indexOf(find, start);
- if (found == -1) {
- found = s.length();
- }
- }
- }
- return sb.toString();
- }
- /**
- * Replaces all occurrences of a string in a buffer with another.
- *
- * @param buf String buffer to act on
- * @param start Ordinal within <code>find</code> to start searching
- * @param find String to find
- * @param replace String to replace it with
- * @return The string buffer
- */
- public static StringBuilder replace(
- StringBuilder buf,
- int start,
- String find,
- String replace)
- {
- // Search and replace from the end towards the start, to avoid O(n ^ 2)
- // copying if the string occurs very commonly.
- int findLength = find.length();
- if (findLength == 0) {
- // Special case where the seek string is empty.
- for (int j = buf.length(); j >= 0; --j) {
- buf.insert(j, replace);
- }
- return buf;
- }
- int k = buf.length();
- while (k > 0) {
- int i = buf.lastIndexOf(find, k);
- if (i < start) {
- break;
- }
- buf.replace(i, i + find.length(), replace);
- // Step back far enough to ensure that the beginning of the section
- // we just replaced does not cause a match.
- k = i - findLength;
- }
- return buf;
- }
- /**
- * Parses an MDX identifier such as <code>[Foo].[Bar].Baz.&Key&Key2</code>
- * and returns the result as a list of segments.
- *
- * @param s MDX identifier
- * @return List of segments
- */
- public static List<Id.Segment> parseIdentifier(String s) {
- return convert(
- org.olap4j.impl.IdentifierParser.parseIdentifier(s));
- }
- /**
- * Converts an array of name parts {"part1", "part2"} into a single string
- * "[part1].[part2]". If the names contain "]" they are escaped as "]]".
- */
- public static String implode(List<Id.Segment> names) {
- StringBuilder sb = new StringBuilder(64);
- for (int i = 0; i < names.size(); i++) {
- if (i > 0) {
- sb.append(".");
- }
- // FIXME: should be:
- // names.get(i).toString(sb);
- // but that causes some tests to fail
- Id.Segment segment = names.get(i);
- switch (segment.getQuoting()) {
- case UNQUOTED:
- segment = new Id.NameSegment(((Id.NameSegment) segment).name);
- }
- segment.toString(sb);
- }
- return sb.toString();
- }
- public static String makeFqName(String name) {
- return quoteMdxIdentifier(name);
- }
- public static String makeFqName(OlapElement parent, String name) {
- if (parent == null) {
- return Util.quoteMdxIdentifier(name);
- } else {
- StringBuilder buf = new StringBuilder(64);
- buf.append(parent.getUniqueName());
- buf.append('.');
- Util.quoteMdxIdentifier(name, buf);
- return buf.toString();
- }
- }
- public static String makeFqName(String parentUniqueName, String name) {
- if (parentUniqueName == null) {
- return quoteMdxIdentifier(name);
- } else {
- StringBuilder buf = new StringBuilder(64);
- buf.append(parentUniqueName);
- buf.append('.');
- Util.quoteMdxIdentifier(name, buf);
- return buf.toString();
- }
- }
- public static OlapElement lookupCompound(
- SchemaReader schemaReader,
- OlapElement parent,
- List<Id.Segment> names,
- boolean failIfNotFound,
- int category)
- {
- return lookupCompound(
- schemaReader, parent, names, failIfNotFound, category,
- MatchType.EXACT);
- }
- /**
- * Resolves a name such as
- * '[Products].[Product Department].[Produce]' by resolving the
- * components ('Products', and so forth) one at a time.
- *
- * @param schemaReader Schema reader, supplies access-control context
- * @param parent Parent element to search in
- * @param names Exploded compound name, such as {"Products",
- * "Product Department", "Produce"}
- * @param failIfNotFound If the element is not found, determines whether
- * to return null or throw an error
- * @param category Type of returned element, a {@link Category} value;
- * {@link Category#Unknown} if it doesn't matter.
- *
- * @pre parent != null
- * @post !(failIfNotFound && return == null)
- *
- * @see #parseIdentifier(String)
- */
- public static OlapElement lookupCompound(
- SchemaReader schemaReader,
- OlapElement parent,
- List<Id.Segment> names,
- boolean failIfNotFound,
- int category,
- MatchType matchType)
- {
- Util.assertPrecondition(parent != null, "parent != null");
- if (LOGGER.isDebugEnabled()) {
- StringBuilder buf = new StringBuilder(64);
- buf.append("Util.lookupCompound: ");
- buf.append("parent.name=");
- buf.append(parent.getName());
- buf.append(", category=");
- buf.append(Category.instance.getName(category));
- buf.append(", names=");
- quoteMdxIdentifier(names, buf);
- LOGGER.debug(buf.toString());
- }
- // First look up a member from the cache of calculated members
- // (cubes and queries both have them).
- switch (category) {
- case Category.Member:
- case Category.Unknown:
- Member member = schemaReader.getCalculatedMember(names);
- if (member != null) {
- return member;
- }
- }
- // Likewise named set.
- switch (category) {
- case Category.Set:
- case Category.Unknown:
- NamedSet namedSet = schemaReader.getNamedSet(names);
- if (namedSet != null) {
- return namedSet;
- }
- }
- // Now resolve the name one part at a time.
- for (int i = 0; i < names.size(); i++) {
- OlapElement child;
- Id.NameSegment name;
- if (names.get(i) instanceof Id.NameSegment) {
- name = (Id.NameSegment) names.get(i);
- child = schemaReader.getElementChild(parent, name, matchType);
- } else if (parent instanceof RolapLevel
- && names.get(i) instanceof Id.KeySegment
- && names.get(i).getKeyParts().size() == 1)
- {
- // The following code is for SsasCompatibleNaming=false.
- // Continues the very limited support for key segments in
- // mondrian-3.x. To be removed in mondrian-4, when
- // SsasCompatibleNaming=true is the only option.
- final Id.KeySegment keySegment = (Id.KeySegment) names.get(i);
- name = keySegment.getKeyParts().get(0);
- final List<Member> levelMembers =
- schemaReader.getLevelMembers(
- (Level) parent, false);
- child = null;
- for (Member member : levelMembers) {
- if (((RolapMember) member).getKey().toString().equals(
- name.getName()))
- {
- child = member;
- break;
- }
- }
- } else {
- name = null;
- child = schemaReader.getElementChild(parent, name, matchType);
- }
- // if we're doing a non-exact match, and we find a non-exact
- // match, then for an after match, return the first child
- // of each subsequent level; for a before match, return the
- // last child
- if (child instanceof Member
- && !matchType.isExact()
- && !Util.equalName(child.getName(), name.getName()))
- {
- Member bestChild = (Member) child;
- for (int j = i + 1; j < names.size(); j++) {
- List<Member> childrenList =
- schemaReader.getMemberChildren(bestChild);
- FunUtil.hierarchizeMemberList(childrenList, false);
- if (matchType == MatchType.AFTER) {
- bestChild = childrenList.get(0);
- } else {
- bestChild =
- childrenList.get(childrenList.size() - 1);
- }
- if (bestChild == null) {
- child = null;
- break;
- }
- }
- parent = bestChild;
- break;
- }
- if (child == null) {
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug(
- "Util.lookupCompound: "
- + "parent.name="
- + parent.getName()
- + " has no child with name="
- + name);
- }
- if (!failIfNotFound) {
- return null;
- } else if (category == Category.Member) {
- throw MondrianResource.instance().MemberNotFound.ex(
- quoteMdxIdentifier(names));
- } else {
- throw MondrianResource.instance().MdxChildObjectNotFound
- .ex(name.toString(), parent.getQualifiedName());
- }
- }
- parent = child;
- if (matchType == MatchType.EXACT_SCHEMA) {
- matchType = MatchType.EXACT;
- }
- }
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug(
- "Util.lookupCompound: "
- + "found child.name="
- + parent.getName()
- + ", child.class="
- + parent.getClass().getName());
- }
- switch (category) {
- case Category.Dimension:
- if (parent instanceof Dimension) {
- return parent;
- } else if (parent instanceof Hierarchy) {
- return parent.getDimension();
- } else if (failIfNotFound) {
- throw Util.newError(
- "Can not find dimension '" + implode(names) + "'");
- } else {
- return null;
- }
- case Category.Hierarchy:
- if (parent instanceof Hierarchy) {
- return parent;
- } else if (parent instanceof Dimension) {
- return parent.getHierarchy();
- } else if (failIfNotFound) {
- throw Util.newError(
- "Can not find hierarchy '" + implode(names) + "'");
- } else {
- return null;
- }
- case Category.Level:
- if (parent instanceof Level) {
- return parent;
- } else if (failIfNotFound) {
- throw Util.newError(
- "Can not find level '" + implode(names) + "'");
- } else {
- return null;
- }
- case Category.Member:
- if (parent instanceof Member) {
- return parent;
- } else if (failIfNotFound) {
- throw MondrianResource.instance().MdxCantFindMember.ex(
- implode(names));
- } else {
- return null;
- }
- case Category.Unknown:
- assertPostcondition(parent != null, "return != null");
- return parent;
- default:
- throw newInternal("Bad switch " + category);
- }
- }
- public static OlapElement lookup(Query q, List<Id.Segment> nameParts) {
- final Exp exp = lookup(q, nameParts, false);
- if (exp instanceof MemberExpr) {
- MemberExpr memberExpr = (MemberExpr) exp;
- return memberExpr.getMember();
- } else if (exp instanceof LevelExpr) {
- LevelExpr levelExpr = (LevelExpr) exp;
- return levelExpr.getLevel();
- } else if (exp instanceof HierarchyExpr) {
- HierarchyExpr hierarchyExpr = (HierarchyExpr) exp;
- return hierarchyExpr.getHierarchy();
- } else if (exp instanceof DimensionExpr) {
- DimensionExpr dimensionExpr = (DimensionExpr) exp;
- return dimensionExpr.getDimension();
- } else {
- throw Util.newInternal("Not an olap element: " + exp);
- }
- }
- /**
- * Converts an identifier into an expression by resolving its parts into
- * an OLAP object (dimension, hierarchy, level or member) within the
- * context of a query.
- *
- * <p>If <code>allowProp</code> is true, also allows property references
- * from valid members, for example
- * <code>[Measures].[Unit Sales].FORMATTED_VALUE</code>.
- * In this case, the result will be a {@link mondrian.mdx.ResolvedFunCall}.
- *
- * @param q Query expression belongs to
- * @param nameParts Parts of the identifier
- * @param allowProp Whether to allow property references
- * @return OLAP object or property reference
- */
- public static Exp lookup(
- Query q,
- List<Id.Segment> nameParts,
- boolean allowProp)
- {
- return lookup(q, q.getSchemaReader(true), nameParts, allowProp);
- }
- /**
- * Converts an identifier into an expression by resolving its parts into
- * an OLAP object (dimension, hierarchy, level or member) within the
- * context of a query.
- *
- * <p>If <code>allowProp</code> is true, also allows property references
- * from valid members, for example
- * <code>[Measures].[Unit Sales].FORMATTED_VALUE</code>.
- * In this case, the result will be a {@link ResolvedFunCall}.
- *
- * @param q Query expression belongs to
- * @param schemaReader Schema reader
- * @param segments Parts of the identifier
- * @param allowProp Whether to allow property references
- * @return OLAP object or property reference
- */
- public static Exp lookup(
- Query q,
- SchemaReader schemaReader,
- List<Id.Segment> segments,
- boolean allowProp)
- {
- // First, look for a calculated member defined in the query.
- final String fullName = quoteMdxIdentifier(segments);
- // Look for any kind of object (member, level, hierarchy,
- // dimension) in the cube. Use a schema reader without restrictions.
- final SchemaReader schemaReaderSansAc =
- schemaReader.withoutAccessControl().withLocus();
- final Cube cube = q.getCube();
- OlapElement olapElement =
- schemaReaderSansAc.lookupCompound(
- cube, segments, false, Category.Unknown);
- if (olapElement != null) {
- Role role = schemaReader.getRole();
- if (!role.canAccess(olapElement)) {
- olapElement = null;
- }
- if (olapElement instanceof Member) {
- olapElement =
- schemaReader.substitute((Member) olapElement);
- }
- }
- if (olapElement == null) {
- if (allowProp && segments.size() > 1) {
- List<Id.Segment> segmentsButOne =
- segments.subList(0, segments.size() - 1);
- final Id.Segment lastSegment = last(segments);
- final String propertyName =
- lastSegment instanceof Id.NameSegment
- ? ((Id.NameSegment) lastSegment).getName()
- : null;
- final Member member =
- (Member) schemaReaderSansAc.lookupCompound(
- cube, segmentsButOne, false, Category.Member);
- if (member != null
- && propertyName != null
- && isValidProperty(propertyName, member.getLevel()))
- {
- return new UnresolvedFunCall(
- propertyName, Syntax.Property, new Exp[] {
- createExpr(member)});
- }
- final Level level =
- (Level) schemaReaderSansAc.lookupCompound(
- cube, segmentsButOne, false, Category.Level);
- if (level != null
- && propertyName != null
- && isValidProperty(propertyName, level))
- {
- return new UnresolvedFunCall(
- propertyName, Syntax.Property, new Exp[] {
- createExpr(level)});
- }
- }
- // if we're in the middle of loading the schema, the property has
- // been set to ignore invalid members, and the member is
- // non-existent, return the null member corresponding to the
- // hierarchy of the element we're looking for; locate the
- // hierarchy by incrementally truncating the name of the element
- if (q.ignoreInvalidMembers()) {
- int nameLen = segments.size() - 1;
- olapElement = null;
- while (nameLen > 0 && olapElement == null) {
- List<Id.Segment> partialName =
- segments.subList(0, nameLen);
- olapElement = schemaReaderSansAc.lookupCompound(
- cube, partialName, false, Category.Unknown);
- nameLen--;
- }
- if (olapElement != null) {
- olapElement = olapElement.getHierarchy().getNullMember();
- } else {
- throw MondrianResource.instance().MdxChildObjectNotFound.ex(
- fullName, cube.getQualifiedName());
- }
- } else {
- throw MondrianResource.instance().MdxChildObjectNotFound.ex(
- fullName, cube.getQualifiedName());
- }
- }
- // keep track of any measure members referenced; these will be used
- // later to determine if cross joins on virtual cubes can be
- // processed natively
- q.addMeasuresMembers(olapElement);
- return createExpr(olapElement);
- }
- /**
- * Looks up a cube in a schema reader.
- *
- * @param cubeName Cube name
- * @param fail Whether to fail if not found.
- * @return Cube, or null if not found
- */
- static Cube lookupCube(
- SchemaReader schemaReader,
- String cubeName,
- boolean fail)
- {
- for (Cube cube : schemaReader.getCubes()) {
- if (Util.compareName(cube.getName(), cubeName) == 0) {
- return cube;
- }
- }
- if (fail) {
- throw MondrianResource.instance().MdxCubeNotFound.ex(cubeName);
- }
- return null;
- }
- /**
- * Converts an olap element (dimension, hierarchy, level or member) into
- * an expression representing a usage of that element in an MDX statement.
- */
- public static Exp createExpr(OlapElement element)
- {
- if (element instanceof Member) {
- Member member = (Member) element;
- return new MemberExpr(member);
- } else if (element instanceof Level) {
- Level level = (Level) element;
- return new LevelExpr(level);
- } else if (element instanceof Hierarchy) {
- Hierarchy hierarchy = (Hierarchy) element;
- return new HierarchyExpr(hierarchy);
- } else if (element instanceof Dimension) {
- Dimension dimension = (Dimension) element;
- return new DimensionExpr(dimension);
- } else if (element instanceof NamedSet) {
- NamedSet namedSet = (NamedSet) element;
- return new NamedSetExpr(namedSet);
- } else {
- throw Util.newInternal("Unexpected element type: " + element);
- }
- }
- public static Member lookupHierarchyRootMember(
- SchemaReader reader, Hierarchy hierarchy, Id.NameSegment memberName)
- {
- return lookupHierarchyRootMember(
- reader, hierarchy, memberName, MatchType.EXACT);
- }
- /**
- * Finds a root member of a hierarchy with a given name.
- *
- * @param hierarchy Hierarchy
- * @param memberName Name of root member
- * @return Member, or null if not found
- */
- public static Member lookupHierarchyRootMember(
- SchemaReader reader,
- Hierarchy hierarchy,
- Id.NameSegment memberName,
- MatchType matchType)
- {
- // Lookup member at first level.
- //
- // Don't use access control. Suppose we cannot see the 'nation' level,
- // we still want to be able to resolve '[Customer].[USA].[CA]'.
- List<Member> rootMembers = reader.getHierarchyRootMembers(hierarchy);
- // if doing an inexact search on a non-all hierarchy, create
- // a member corresponding to the name we're searching for so
- // we can use it in a hierarchical search
- Member searchMember = null;
- if (!matchType.isExact()
- && !hierarchy.hasAll()
- && !rootMembers.isEmpty())
- {
- searchMember =
- hierarchy.createMember(
- null,
- rootMembers.get(0).getLevel(),
- memberName.name,
- null);
- }
- int bestMatch = -1;
- int k = -1;
- for (Member rootMember : rootMembers) {
- ++k;
- int rc;
- // when searching on the ALL hierarchy, match must be exact
- if (matchType.isExact() || hierarchy.hasAll()) {
- rc = rootMember.getName().compareToIgnoreCase(memberName.name);
- } else {
- rc = FunUtil.compareSiblingMembers(
- rootMember,
- searchMember);
- }
- if (rc == 0) {
- return rootMember;
- }
- if (!hierarchy.hasAll()) {
- if (matchType == MatchType.BEFORE) {
- if (rc < 0
- && (bestMatch == -1
- || FunUtil.compareSiblingMembers(
- rootMember,
- rootMembers.get(bestMatch)) > 0))
- {
- bestMatch = k;
- }
- } else if (matchType == MatchType.AFTER) {
- if (rc > 0
- && (bestMatch == -1
- || FunUtil.compareSiblingMembers(
- rootMember,
- rootMembers.get(bestMatch)) < 0))
- {
- bestMatch = k;
- }
- }
- }
- }
- if (matchType == MatchType.EXACT_SCHEMA) {
- return null;
- }
- if (matchType != MatchType.EXACT && bestMatch != -1) {
- return rootMembers.get(bestMatch);
- }
- // If the first level is 'all', lookup member at second level. For
- // example, they could say '[USA]' instead of '[(All
- // Customers)].[USA]'.
- return (rootMembers.size() > 0 && rootMembers.get(0).isAll())
- ? reader.lookupMemberChildByName(
- rootMembers.get(0),
- memberName,
- matchType)
- : null;
- }
- /**
- * Finds a named level in this hierarchy. Returns null if there is no
- * such level.
- */
- public static Level lookupHierarchyLevel(Hierarchy hierarchy, String s) {
- final Level[] levels = hierarchy.getLevels();
- for (Level level : levels) {
- if (level.getName().equalsIgnoreCase(s)) {
- return level;
- }
- }
- return null;
- }
- /**
- * Finds the zero based ordinal of a Member among its siblings.
- */
- public static int getMemberOrdinalInParent(
- SchemaReader reader,
- Member member)
- {
- Member parent = member.getParentMember();
- List<Member> siblings =
- (parent == null)
- ? reader.getHierarchyRootMembers(member.getHierarchy())
- : reader.getMemberChildren(parent);
- for (int i = 0; i < siblings.size(); i++) {
- if (siblings.get(i).equals(member)) {
- return i;
- }
- }
- throw Util.newInternal(
- "could not find member " + member + " amongst its siblings");
- }
- /**
- * returns the first descendant on the level underneath parent.
- * If parent = [Time].[1997] and level = [Time].[Month], then
- * the member [Time].[1997].[Q1].[1] will be returned
- */
- public static Member getFirstDescendantOnLevel(
- SchemaReader reader,
- Member parent,
- Level level)
- {
- Member m = parent;
- while (m.getLevel() != level) {
- List<Member> children = reader.getMemberChildren(m);
- m = children.get(0);
- }
- return m;
- }
- /**
- * Returns whether a string is null or empty.
- */
- public static boolean isEmpty(String s) {
- return (s == null) || (s.length() == 0);
- }
- /**
- * Encloses a value in single-quotes, to make a SQL string value. Examples:
- * <code>singleQuoteForSql(null)</code> yields <code>NULL</code>;
- * <code>singleQuoteForSql("don't")</code> yields <code>'don''t'</code>.
- */
- public static String singleQuoteString(String val) {
- StringBuilder buf = new StringBuilder(64);
- singleQuoteString(val, buf);
- return buf.toString();
- }
- /**
- * Encloses a value in single-quotes, to make a SQL string value. Examples:
- * <code>singleQuoteForSql(null)</code> yields <code>NULL</code>;
- * <code>singleQuoteForSql("don't")</code> yields <code>'don''t'</code>.
- */
- public static void singleQuoteString(String val, StringBuilder buf) {
- buf.append('\'');
- String s0 = replace(val, "'", "''");
- buf.append(s0);
- buf.append('\'');
- }
- /**
- * Creates a random number generator.
- *
- * @param seed Seed for random number generator.
- * If 0, generate a seed from the system clock and print the value
- * chosen. (This is effectively non-deterministic.)
- * If -1, generate a seed from an internal random number generator.
- * (This is deterministic, but ensures that different tests have
- * different seeds.)
- *
- * @return A random number generator.
- */
- public static Random createRandom(long seed) {
- if (seed == 0) {
- seed = new Random().nextLong();
- System.out.println("random: seed=" + seed);
- } else if (seed == -1 && metaRandom != null) {
- seed = metaRandom.nextLong();
- }
- return new Random(seed);
- }
- /**
- * Returns whether a property is valid for a member of a given level.
- * It is valid if the property is defined at the level or at
- * an ancestor level, or if the property is a standard property such as
- * "FORMATTED_VALUE".
- *
- * @param propertyName Property name
- * @param level Level
- * @return Whether property is valid
- */
- public static boolean isValidProperty(
- String propertyName,
- Level level)
- {
- return lookupProperty(level, propertyName) != null;
- }
- /**
- * Finds a member property called <code>propertyName</code> at, or above,
- * <code>level</code>.
- */
- public static Property lookupProperty(
- Level level,
- String propertyName)
- {
- do {
- Property[] properties = level.getProperties();
- for (Property property : properties) {
- if (property.getName().equals(propertyName)) {
- return property;
- }
- }
- level = level.getParentLevel();
- } while (level != null);
- // Now try a standard property.
- boolean caseSensitive =
- MondrianProperties.instance().CaseSensitive.get();
- final Property property = Property.lookup(propertyName, caseSensitive);
- if (property != null
- && property.isMemberProperty()
- && property.isStandard())
- {
- return property;
- }
- return null;
- }
- /**
- * Insert a call to this method if you want to flag a piece of
- * undesirable code.
- *
- * @deprecated
- */
- public static <T> T deprecated(T reason) {
- throw new UnsupportedOperationException(reason.toString());
- }
- /**
- * Insert a call to this method if you want to flag a piece of
- * undesirable code.
- *
- * @deprecated
- */
- public static <T> T deprecated(T reason, boolean fail) {
- if (fail) {
- throw new UnsupportedOperationException(reason.toString());
- } else {
- return reason;
- }
- }
- public static List<Member> addLevelCalculatedMembers(
- SchemaReader reader,
- Level level,
- List<Member> members)
- {
- List<Member> calcMembers =
- reader.getCalculatedMembers(level.getHierarchy());
- List<Member> calcMembersInThisLevel = new ArrayList<Member>();
- for (Member calcMember : calcMembers) {
- if (calcMember.getLevel().equals(level)) {
- calcMembersInThisLevel.add(calcMember);
- }
- }
- if (!calcMembersInThisLevel.isEmpty()) {
- List<Member> newMemberList =
- new ConcatenableList<Member>();
- newMemberList.addAll(members);
- newMemberList.addAll(calcMembersInThisLevel);
- return newMemberList;
- }
- return members;
- }
- /**
- * Returns an exception which indicates that a particular piece of
- * functionality should work, but a developer has not implemented it yet.
- */
- public static RuntimeException needToImplement(Object o) {
- throw new UnsupportedOperationException("need to implement " + o);
- }
- /**
- * Returns an exception indicating that we didn't expect to find this value
- * here.
- */
- public static <T extends Enum<T>> RuntimeException badValue(
- Enum<T> anEnum)
- {
- return Util.newInternal(
- "Was not expecting value '" + anEnum
- + "' for enumeration '" + anEnum.getDeclaringClass().getName()
- + "' in this context");
- }
- /**
- * Masks Mondrian's version number from a string.
- *
- * @param str String
- * @return String with each occurrence of mondrian's version number
- * (e.g. "2.3.0.0") replaced with "${mondrianVersion}"
- */
- public static String maskVersion(String str) {
- MondrianServer.MondrianVersion mondrianVersion =
- MondrianServer.forId(null).getVersion();
- String versionString = mondrianVersion.getVersionString();
- return replace(str, versionString, "${mondrianVersion}");
- }
- /**
- * Converts a list of SQL-style patterns into a Java regular expression.
- *
- * <p>For example, {"Foo_", "Bar%BAZ"} becomes "Foo.|Bar.*BAZ".
- *
- * @param wildcards List of SQL-style wildcard expressions
- * @return Regular expression
- */
- public static String wildcardToRegexp(List<String> wildcards) {
- StringBuilder buf = new StringBuilder();
- for (String value : wildcards) {
- if (buf.length() > 0) {
- buf.append('|');
- }
- int i = 0;
- while (true) {
- int percent = value.indexOf('%', i);
- int underscore = value.indexOf('_', i);
- if (percent == -1 && underscore == -1) {
- if (i < value.length()) {
- …
Large files files are truncated, but you can click here to view the full file