/*
//
// Licensed to Benedikt Kämpgen under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Benedikt Kämpgen licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
package org.olap4j.driver.olap4ld;
import org.olap4j.OlapException;
import org.olap4j.driver.olap4ld.Olap4ldCube;
import org.olap4j.driver.olap4ld.Olap4ldDimension;
import org.olap4j.driver.olap4ld.Olap4ldHierarchy;
import org.olap4j.driver.olap4ld.Olap4ldLevel;
import org.olap4j.driver.olap4ld.Olap4ldMeasure;
import org.olap4j.driver.olap4ld.Olap4ldMember;
import org.olap4j.driver.olap4ld.Olap4ldSchema;
import org.olap4j.driver.olap4ld.helper.Olap4ldLinkedDataUtil;
import org.olap4j.impl.*;
import org.olap4j.mdx.*;
import org.olap4j.metadata.*;
import org.semanticweb.yars.nx.Literal;
import org.semanticweb.yars.nx.Node;
import org.semanticweb.yars.nx.Variable;
import java.util.*;
import java.lang.ref.SoftReference;
/**
* Implementation of {@link Cube} for XML/A providers.
*
* @author jhyde, bkaempgen
* @version $Id: XmlaOlap4jCube.java 448 2011-04-13 00:32:40Z jhyde $
* @since Dec 4, 2007
*/
class Olap4ldCube implements Cube, Named {
final Olap4ldSchema olap4jSchema;
private final String name;
private final String caption;
private final String description;
final NamedList<Olap4ldDimension> dimensions;
final Map<String, Olap4ldDimension> dimensionsByUname = new HashMap<String, Olap4ldDimension>();
private NamedList<Olap4ldHierarchy> hierarchies = null;
final Map<String, Olap4ldHierarchy> hierarchiesByUname = new HashMap<String, Olap4ldHierarchy>();
final Map<String, Olap4ldLevel> levelsByUname = new HashMap<String, Olap4ldLevel>();
final NamedList<Olap4ldMeasure> measures;
private final NamedList<Olap4ldNamedSet> namedSets;
private final MetadataReader metadataReader;
/**
* Creates an XmlaOlap4jCube.
*
* @param olap4jSchema
* Schema
* @param name
* Name
* @param caption
* Caption
* @param description
* Description
* @throws org.olap4j.OlapException
* on error
*/
Olap4ldCube(Olap4ldSchema olap4jSchema, String name, String caption,
String description) throws OlapException {
assert olap4jSchema != null;
assert description != null;
assert name != null;
this.olap4jSchema = olap4jSchema;
this.name = name;
this.caption = caption;
this.description = description;
final Olap4ldConnection.Context context = new Olap4ldConnection.Context(
this, null, null, null);
String[] restrictions = { "CATALOG_NAME",
olap4jSchema.olap4jCatalog.getName(), "SCHEMA_NAME",
olap4jSchema.getName(), "CUBE_NAME", getName() };
this.dimensions = new DeferredNamedListImpl<Olap4ldDimension>(
Olap4ldConnection.MetadataRequest.MDSCHEMA_DIMENSIONS,
context, new Olap4ldConnection.DimensionHandler(this),
restrictions);
// TODO: Why do I need to populate the entire cube, anyway?
// int pop = dimensions.size();
// TODO: We simply do not return named sets
// populate named sets
namedSets = new DeferredNamedListImpl<Olap4ldNamedSet>(
Olap4ldConnection.MetadataRequest.MDSCHEMA_SETS, context,
new Olap4ldConnection.NamedSetHandler(), restrictions);
// TODO: I do not want to populate measures up front, since then I need
// to populate everything.
// populate measures up front; a measure is needed in every query
// try {
// olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection
// .populateList(
// measures,
// context,
// LdOlap4jConnection.MetadataRequest.MDSCHEMA_MEASURES,
// new LdOlap4jConnection.MeasureHandler(),
// restrictions);
// } catch (OlapException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
/*
* A strange concept is that any cube has a list of measures, and also a
* dimension-hierarchy-level "Measures", that contains the same
* measures.
*
* TODO: Currently, special type dimensions are simply also queried from
* RDF. What I could do is manually add the special type dimension
* "Measures" to any cube. For now, we keep it since it does not make
* problems.
*/
this.measures = new DeferredNamedListImpl<Olap4ldMeasure>(
Olap4ldConnection.MetadataRequest.MDSCHEMA_MEASURES, context,
new Olap4ldConnection.MeasureHandler(), restrictions);
// MetadataReader which is not part of the olap4j api
this.metadataReader = new CachingMetadataReader(
new RawMetadataReader(), measures);
}
public Schema getSchema() {
return olap4jSchema;
}
public String getName() {
return name;
}
/**
* The unique name of a cube is simply the name;
*
* Note, squared brackets are added by the system and not added to a cube
* itself.
*/
public String getUniqueName() {
// return "[" + name + "]";
return name;
}
public String getCaption() {
return caption;
}
public String getDescription() {
return description;
}
public boolean isVisible() {
return true;
}
public NamedList<Dimension> getDimensions() {
Olap4ldUtil._log.config("Metadata object getDimensions()...");
return Olap4jUtil.cast(dimensions);
}
public NamedList<Hierarchy> getHierarchies() {
Olap4ldUtil._log.config("Metadata object getHierarchies()...");
// This is a costly operation. It forces the init
// of all dimensions and all hierarchies.
// We defer it to this point.
// Workaround: I use the normal methods and not the attributes.
if (this.hierarchies == null) {
this.hierarchies = new NamedListImpl<Olap4ldHierarchy>();
NamedList<Dimension> theDimensions = this.getDimensions();
for (Dimension dim : theDimensions) {
Olap4ldDimension ldDim = (Olap4ldDimension) dim;
NamedList<Hierarchy> theHierarchies = ldDim.getHierarchies();
for (Hierarchy hierarchy : theHierarchies) {
this.hierarchies.add((Olap4ldHierarchy) hierarchy);
}
}
}
return Olap4jUtil.cast(hierarchies);
}
public boolean isDrillThroughEnabled() {
// XMLA does not implement drillthrough yet.
return false;
}
public List<Measure> getMeasures() {
Olap4ldUtil._log.config("Metadata object getMeasures()...");
return Olap4jUtil.cast(measures);
}
public NamedList<NamedSet> getSets() {
return Olap4jUtil.cast(namedSets);
}
public Collection<Locale> getSupportedLocales() {
return Collections.singletonList(Locale.getDefault());
}
public Member lookupMember(List<IdentifierSegment> segmentList)
throws OlapException {
StringBuilder buf = new StringBuilder();
// Segments are just concatenated to make up the unique name.
for (IdentifierSegment segment : segmentList) {
if (buf.length() > 0) {
buf.append('.');
}
// We are looking for names, i.e. strings with square brackets
// buf.append(segment.getName());
buf.append(segment.toString());
}
final String uniqueName = buf.toString();
// MetadataReader is used to find the actual member
return getMetadataReader().lookupMemberByUniqueName(uniqueName);
}
/**
* Returns this cube's metadata reader.
*
* <p>
* Not part of public olap4j API.
*
* @return metadata reader
*/
public MetadataReader getMetadataReader() {
return metadataReader;
}
public List<Member> lookupMembers(Set<Member.TreeOp> treeOps,
List<IdentifierSegment> nameParts) throws OlapException {
StringBuilder buf = new StringBuilder();
// Segments are just concatenated to make up the unique name.
for (IdentifierSegment namePart : nameParts) {
if (buf.length() > 0) {
buf.append('.');
}
buf.append(namePart);
}
final String uniqueName = buf.toString();
final List<Olap4ldMember> list = new ArrayList<Olap4ldMember>();
// Metadata reader is used for finding all members
getMetadataReader().lookupMemberRelatives(treeOps, uniqueName, list);
return Olap4jUtil.cast(list);
}
/**
* Abstract implementation of MemberReader that delegates all operations to
* an underlying MemberReader.
*/
private static abstract class DelegatingMetadataReader implements
MetadataReader {
private final MetadataReader metadataReader;
/**
* Creates a DelegatingMetadataReader.
*
* @param metadataReader
* Underlying metadata reader
*/
DelegatingMetadataReader(MetadataReader metadataReader) {
this.metadataReader = metadataReader;
}
public Olap4ldMember lookupMemberByUniqueName(String memberUniqueName)
throws OlapException {
return metadataReader.lookupMemberByUniqueName(memberUniqueName);
}
public void lookupMembersByUniqueName(List<String> memberUniqueNames,
Map<String, Olap4ldMember> memberMap) throws OlapException {
metadataReader.lookupMembersByUniqueName(memberUniqueNames,
memberMap);
}
public void lookupMemberRelatives(Set<Member.TreeOp> treeOps,
String memberUniqueName, List<Olap4ldMember> list)
throws OlapException {
metadataReader.lookupMemberRelatives(treeOps, memberUniqueName,
list);
}
public List<Olap4ldMember> getLevelMembers(Olap4ldLevel level)
throws OlapException {
return metadataReader.getLevelMembers(level);
}
}
/**
* Implementation of MemberReader that reads from an underlying member
* reader and caches the results.
*
* <p>
* Caches are {@link Map}s containing {@link java.lang.ref.SoftReference}s
* to cached objects, so can be cleared when memory is in short supply.
*/
private static class CachingMetadataReader extends DelegatingMetadataReader {
private final Map<String, SoftReference<Olap4ldMember>> memberMap = new HashMap<String, SoftReference<Olap4ldMember>>();
private final Map<Olap4ldLevel, SoftReference<List<Olap4ldMember>>> levelMemberListMap = new HashMap<Olap4ldLevel, SoftReference<List<Olap4ldMember>>>();
//private NamedList<Olap4ldMeasure> measures;
/**
* Creates a CachingMetadataReader.
*
* @param metadataReader
* Underlying metadata reader
* @param measures
* Map of measures by unique name, inherited from the cube
* and used read-only by this reader
*/
CachingMetadataReader(MetadataReader metadataReader,
NamedList<Olap4ldMeasure> measures) {
super(metadataReader);
//this.measures = measures;
}
public Olap4ldMember lookupMemberByUniqueName(String memberUniqueName)
throws OlapException {
// We do not have a measure map any more, so we look in measures,
// // First, look in measures map.
// TODO: I will always recognize a not populated measure list, by it
// being empty. Possibly, it does not make sense to query for
// measures here, also, since
// we have a list of measures for a cube, and we have a list of
// measures for the measure dimension. Thus, we look here in the
// cube measure list.
// if (!measures.isEmpty()) {
// Olap4ldMeasure measure = measures.get(memberUniqueName);
// if (measure != null) {
// return measure;
// }
// }
// Next, look in cache.
final SoftReference<Olap4ldMember> memberRef = memberMap
.get(memberUniqueName);
if (memberRef != null) {
final Olap4ldMember member = memberRef.get();
if (member != null) {
return member;
}
}
// Lastly, ask super class
final Olap4ldMember member = super
.lookupMemberByUniqueName(memberUniqueName);
if (member != null
&& member.getDimension().type != Dimension.Type.MEASURE) {
memberMap.put(memberUniqueName,
new SoftReference<Olap4ldMember>(member));
}
// Can null
return member;
}
public void lookupMembersByUniqueName(List<String> memberUniqueNames,
Map<String, Olap4ldMember> memberMap) throws OlapException {
final ArrayList<String> remainingMemberUniqueNames = new ArrayList<String>();
for (String memberUniqueName : memberUniqueNames) {
// First, look in measures map.
// TODO: I will always recognize a not populated measure list,
// by it
// being empty.
// if (!measures.isEmpty()) {
// Olap4ldMeasure measure = measures.get(memberUniqueName);
// if (measure != null) {
// memberMap.put(memberUniqueName, measure);
// continue;
// }
// }
// Next, look in cache.
final SoftReference<Olap4ldMember> memberRef = this.memberMap
.get(memberUniqueName);
final Olap4ldMember member;
if (memberRef != null && (member = memberRef.get()) != null) {
memberMap.put(memberUniqueName, member);
continue;
}
remainingMemberUniqueNames.add(memberUniqueName);
}
// If any of the member names were not in the cache, look them up
// by delegating.
if (!remainingMemberUniqueNames.isEmpty()) {
super.lookupMembersByUniqueName(remainingMemberUniqueNames,
memberMap);
// Add the previously missing members into the cache.
for (String memberName : remainingMemberUniqueNames) {
Olap4ldMember member = memberMap.get(memberName);
if (member != null) {
if (!(member instanceof Measure)
&& member.getDimension().type != Dimension.Type.MEASURE) {
this.memberMap.put(memberName,
new SoftReference<Olap4ldMember>(member));
}
}
}
}
}
public List<Olap4ldMember> getLevelMembers(Olap4ldLevel level)
throws OlapException {
final SoftReference<List<Olap4ldMember>> memberListRef = levelMemberListMap
.get(level);
if (memberListRef != null) {
final List<Olap4ldMember> memberList = memberListRef.get();
if (memberList != null) {
return memberList;
}
}
final List<Olap4ldMember> memberList = super
.getLevelMembers(level);
if (level.olap4jHierarchy.olap4jDimension.type != Dimension.Type.MEASURE) {
levelMemberListMap.put(level,
new SoftReference<List<Olap4ldMember>>(memberList));
}
return memberList;
}
}
/**
* Implementation of MetadataReader that reads from the XMLA provider,
* without caching.
*/
private class RawMetadataReader implements MetadataReader {
public Olap4ldMember lookupMemberByUniqueName(String memberUniqueName)
throws OlapException {
NamedList<Olap4ldMember> list = new NamedListImpl<Olap4ldMember>();
// So, we are querying for the member self with a specific unique
// name, that shall be put in an empty list
lookupMemberRelatives(Olap4jUtil.enumSetOf(Member.TreeOp.SELF),
memberUniqueName, list);
switch (list.size()) {
case 0:
return null;
case 1:
return list.get(0);
default:
throw new IllegalArgumentException(
"more than one member with unique name '"
+ memberUniqueName + "'");
}
}
public void lookupMembersByUniqueName(List<String> memberUniqueNames,
Map<String, Olap4ldMember> memberMap) throws OlapException {
if (olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection
.getDatabase().indexOf("Provider=Mondrian") != -1) {
mondrianMembersLookup(memberUniqueNames, memberMap);
} else {
genericMembersLookup(memberUniqueNames, memberMap);
}
}
/**
* Looks up members; optimized for Mondrian servers.
*
* @param memberUniqueNames
* A list of the members to lookup
* @param memberMap
* Output map of members keyed by unique name
* @throws OlapException
* Gets thrown for communication errors
*/
private void mondrianMembersLookup(List<String> memberUniqueNames,
Map<String, Olap4ldMember> memberMap) throws OlapException {
final Olap4ldConnection.Context context = new Olap4ldConnection.Context(
Olap4ldCube.this, null, null, null);
final List<Olap4ldMember> memberList = new ArrayList<Olap4ldMember>();
olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData
.populateList(
memberList,
context,
Olap4ldConnection.MetadataRequest.MDSCHEMA_MEMBERS,
new Olap4ldConnection.MemberHandler(),
new Object[] { "CATALOG_NAME",
olap4jSchema.olap4jCatalog.getName(),
"SCHEMA_NAME", olap4jSchema.getName(),
"CUBE_NAME", getName(),
"MEMBER_UNIQUE_NAME", memberUniqueNames });
for (Olap4ldMember member : memberList) {
if (member != null) {
memberMap.put(member.getUniqueName(), member);
}
}
}
/**
* Looks up members.
*
* @param memberUniqueNames
* A list of the members to lookup
* @param memberMap
* Output map of members keyed by unique name
* @throws OlapException
* Gets thrown for communication errors
*/
private void genericMembersLookup(List<String> memberUniqueNames,
Map<String, Olap4ldMember> memberMap) throws OlapException {
// Iterates through member names
for (String currentMemberName : memberUniqueNames) {
// Only lookup if it is not in the map yet
if (!memberMap.containsKey(currentMemberName)) {
Olap4ldMember member = this
.lookupMemberByUniqueName(currentMemberName);
// Null members might mean calculated members
if (member != null) {
memberMap.put(member.getUniqueName(), member);
}
}
}
}
public void lookupMemberRelatives(Set<Member.TreeOp> treeOps,
String memberUniqueName, List<Olap4ldMember> list)
throws OlapException {
final Olap4ldConnection.Context context = new Olap4ldConnection.Context(
Olap4ldCube.this, null, null, null);
int treeOpMask = 0;
for (Member.TreeOp treeOp : treeOps) {
treeOpMask |= treeOp.xmlaOrdinal();
}
olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData
.populateList(
list,
context,
Olap4ldConnection.MetadataRequest.MDSCHEMA_MEMBERS,
new Olap4ldConnection.MemberHandler(),
new Object[] { "CATALOG_NAME",
olap4jSchema.olap4jCatalog.getName(),
"SCHEMA_NAME", olap4jSchema.getName(),
"CUBE_NAME", getName(),
"MEMBER_UNIQUE_NAME", memberUniqueName,
"TREE_OP", String.valueOf(treeOpMask) });
}
public List<Olap4ldMember> getLevelMembers(Olap4ldLevel level)
throws OlapException {
assert level.olap4jHierarchy.olap4jDimension.olap4jCube == Olap4ldCube.this;
final Olap4ldConnection.Context context = new Olap4ldConnection.Context(
level);
List<Olap4ldMember> list = new ArrayList<Olap4ldMember>();
// If this is a level in the [Measures] dimension, we want to
// return objects that implement the Measure interface. During
// bootstrap, the list will be empty, and we need to return the
// regular Member objects which have the extra properties that are
// returned by MSCHEMA_MEMBERS but not MDSCHEMA_MEASURES.
// TODO: I will always recognize a not populated measure list, by it
// being empty.
switch (level.getDimension().getDimensionType()) {
case MEASURE:
if (!level.olap4jHierarchy.olap4jDimension.olap4jCube.measures
.isEmpty()) {
return Olap4jUtil
.cast(level.olap4jHierarchy.olap4jDimension.olap4jCube.measures);
}
break;
default:
olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData
.populateList(
list,
context,
Olap4ldConnection.MetadataRequest.MDSCHEMA_MEMBERS,
new Olap4ldConnection.MemberHandler(),
new Object[] {
"CATALOG_NAME",
olap4jSchema.olap4jCatalog.getName(),
"SCHEMA_NAME",
olap4jSchema.getName(),
"CUBE_NAME",
getName(),
"DIMENSION_UNIQUE_NAME",
level.olap4jHierarchy.olap4jDimension
.getUniqueName(),
"HIERARCHY_UNIQUE_NAME",
level.olap4jHierarchy.getUniqueName(),
"LEVEL_UNIQUE_NAME",
level.getUniqueName() });
return list;
}
throw new UnsupportedOperationException(
"The right type of dimension should have been found.");
}
}
@Deprecated
public List<Node[]> transformMetadataObject2NxNodes() {
List<Node[]> nodes = new ArrayList<Node[]>();
// Create header
/*
* ?CATALOG_NAME ?SCHEMA_NAME ?CUBE_NAME ?CUBE_TYPE ?CUBE_CAPTION
* ?DESCRIPTION
*/
Node[] header = new Node[] { new Variable("?CATALOG_NAME"),
new Variable("?SCHEMA_NAME"), new Variable("?CUBE_NAME"),
new Variable("?CUBE_TYPE"), new Variable("?CUBE_CAPTION"),
new Variable("?DESCRIPTION") };
nodes.add(header);
Node[] cubenode = new Node[] {
Olap4ldLinkedDataUtil
.convertMDXtoURI(this.getSchema().getCatalog().getName()),
Olap4ldLinkedDataUtil
.convertMDXtoURI(this.getSchema().getName()),
Olap4ldLinkedDataUtil
.convertMDXtoURI(this.getUniqueName()), new Literal("CUBE"),
new Literal(this.getCaption()),
new Literal(this.getDescription()) };
nodes.add(cubenode);
return nodes;
}
}
// End XmlaOlap4jCube.java