/* // // 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 java.io.StringWriter; import java.net.MalformedURLException; import java.net.URL; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.Savepoint; import java.sql.Statement; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.regex.Pattern; import org.olap4j.OlapConnection; import org.olap4j.OlapDatabaseMetaData; import org.olap4j.OlapException; import org.olap4j.OlapStatement; import org.olap4j.PreparedOlapStatement; import org.olap4j.Scenario; import org.olap4j.driver.olap4ld.helper.LdHelper; import org.olap4j.driver.olap4ld.helper.Olap4ldLinkedDataUtil; import org.olap4j.driver.olap4ld.linkeddata.EmbeddedSesameEngine; import org.olap4j.driver.olap4ld.linkeddata.LinkedDataCubesEngine; import org.olap4j.driver.olap4ld.linkeddata.OpenVirtuosoEngine; import org.olap4j.driver.olap4ld.linkeddata.Restrictions; import org.olap4j.driver.olap4ld.linkeddata.SesameEngine; import org.olap4j.driver.olap4ld.proxy.XmlaOlap4jCachedProxy; import org.olap4j.driver.olap4ld.proxy.XmlaOlap4jProxy; import org.olap4j.impl.ConnectStringParser; import org.olap4j.impl.Named; import org.olap4j.impl.Olap4jUtil; import org.olap4j.impl.UnmodifiableArrayList; import org.olap4j.mdx.ParseTreeNode; import org.olap4j.mdx.ParseTreeWriter; import org.olap4j.mdx.SelectNode; import org.olap4j.mdx.parser.MdxParser; import org.olap4j.mdx.parser.MdxParserFactory; import org.olap4j.mdx.parser.MdxValidator; import org.olap4j.mdx.parser.impl.DefaultMdxParserImpl; import org.olap4j.metadata.Catalog; import org.olap4j.metadata.Database; import org.olap4j.metadata.Database.AuthenticationMode; import org.olap4j.metadata.Database.ProviderType; import org.olap4j.metadata.Datatype; import org.olap4j.metadata.Dimension; import org.olap4j.metadata.Hierarchy; import org.olap4j.metadata.Level; import org.olap4j.metadata.Measure; import org.olap4j.metadata.Measure.Aggregator; import org.olap4j.metadata.Member; import org.olap4j.metadata.NamedList; import org.olap4j.metadata.Property; import org.olap4j.metadata.Schema; import org.semanticweb.yars.nx.Node; import org.w3c.dom.Element; /** * Implementation of {@link org.olap4j.OlapConnection} for XML/A providers. * * <p> * This class has sub-classes which implement JDBC 3.0 and JDBC 4.0 APIs; it is * instantiated using {@link Factory#newConnection}. * </p> * * @author jhyde, bkaempgen * @version $Id: XmlaOlap4jConnection.java 471 2011-08-03 21:46:56Z jhyde $ * @since May 23, 2007 */ abstract class Olap4ldConnection implements OlapConnection { /** * Handler for errors. */ final LdHelper helper = new LdHelper(); /** * <p> * Current database. */ private Database olap4jDatabase; /** * <p> * Current catalog. */ private Catalog olap4jCatalog; /** * <p> * Current schema. */ private Schema olap4jSchema; final Olap4ldDatabaseMetaData olap4jDatabaseMetaData; // TODO: Other is different. // private static final String CONNECT_STRING_PREFIX = "jdbc:xmla:"; private static final String CONNECT_STRING_PREFIX = "jdbc:ld:"; final Olap4ldDriver driver; final Factory factory; final XmlaOlap4jProxy proxy; private boolean closed = false; /** * URL of the HTTP server to which to send XML requests. */ final Olap4ldServerInfos serverInfos; private Locale locale; /** * Name of the catalog to which the user wishes to bind this connection. * This value can be set through the JDBC URL or via * {@link Olap4ldConnection#setCatalog(String)} */ private String catalogName; /** * Name of the schema to which the user wishes to bind this connection to. * This value can also be set through the JDBC URL or via * {@link Olap4ldConnection#setSchema(String)} */ private String schemaName; /** * Name of the role that this connection impersonates. */ private String roleName; /** * Name of the database to which the user wishes to bind this connection. * This value can be set through the JDBC URL or via * {@link Olap4ldConnection#setCatalog(String)} */ private String databaseName; private boolean autoCommit; private boolean readOnly; /** * Root of the metadata hierarchy of this connection. */ private final NamedList<Olap4ldDatabase> olapDatabases; private final URL serverUrlObject; // ///////Needed for LD/////////////////// /** * The back end OLAP. * * It basically allows for serialization of a Multidimensional Model and * queries to it. */ // MondrianOlapServerEngine myOlapServer; /** * The data sources from Linked Data for ETL and the domain model. * * It basically allows for connecting to Linked Data, managing of resources. */ LinkedDataCubesEngine myLinkedData; private String url; private ArrayList<String> datasets; private ArrayList<String> datastructuredefinitions; /** * Creates an Olap4j connection an XML/A provider. * * <p> * This method is intentionally package-protected. The public API uses the * traditional JDBC {@link java.sql.DriverManager}. See * {@link org.olap4j.driver.olap4ld.Olap4ldDriver} for more details. * * <p> * Note that this constructor should make zero non-trivial calls, which * could cause deadlocks due to java.sql.DriverManager synchronization * issues. * * @pre acceptsURL(url) * * @param factory * Factory * @param driver * Driver * @param proxy * Proxy object which receives XML requests * @param url * Connect-string URL * @param info * Additional properties * @throws java.sql.SQLException * if there is an error */ Olap4ldConnection(Factory factory, Olap4ldDriver driver, XmlaOlap4jProxy proxy, String url, Properties info) throws SQLException { if (!acceptsURL(url)) { // This is not a URL we can handle. // DriverManager should not have invoked us. throw new AssertionError("does not start with '" + CONNECT_STRING_PREFIX + "'"); } this.url = url; this.factory = factory; this.driver = driver; this.proxy = proxy; // Initialize Linked Data and Olap server part // Required for the logic below to work. assert url.startsWith("jdbc:ld"); // We do not need the mondrian server anymore. // String newurl = url.replaceAll("jdbc:ld:", "jdbc:mondrian:"); // This is the OLAP server // myOlapServer = new MondrianOlapServerEngine(driver, newurl, info); // fills map with databaseName, catalogName, schemaName, serverUrlObject final Map<String, String> map = parseConnectString(url, info); this.databaseName = map.get(Olap4ldDriver.Property.DATABASE.name()); this.catalogName = map.get(Olap4ldDriver.Property.CATALOG.name()); this.schemaName = map.get(Olap4ldDriver.Property.SCHEMA.name()); // Set URL of Triple Store (HTTP) final String serverUrl = map.get(Olap4ldDriver.Property.SERVER.name()); if (serverUrl == null) { throw getHelper().createException( "Connection property '" + Olap4ldDriver.Property.SERVER.name() + "' must be specified"); } try { this.serverUrlObject = new URL(serverUrl); } catch (MalformedURLException e) { throw getHelper().createException(e); } String datasetString = map.get(Olap4ldDriver.Property.DATASETS.name()); // TODO: Tokenizer using Komma ",". StringTokenizer urltokenizer = new StringTokenizer(datasetString, ","); this.datasets = new ArrayList<String>(); while (urltokenizer.hasMoreTokens()) { datasets.add(urltokenizer.nextToken()); } String dsdString = map .get(Olap4ldDriver.Property.DATASTRUCTUREDEFINITIONS.name()); // TODO: Tokenizer using Komma ",". StringTokenizer urltokenizer1 = new StringTokenizer(dsdString, ","); this.datastructuredefinitions = new ArrayList<String>(); while (urltokenizer1.hasMoreTokens()) { datastructuredefinitions.add(urltokenizer1.nextToken()); } initLinkedDataEngine(); // Initialize the SOAP cache if needed // TODO remove? initSoapCache(map); this.serverInfos = new Olap4ldServerInfos() { private String sessionId = null; public String getUsername() { return map.get("user"); } public String getPassword() { return map.get("password"); } public URL getUrl() { return serverUrlObject; } public String getSessionId() { return sessionId; } public void setSessionId(String sessionId) { this.sessionId = sessionId; } }; this.olap4jDatabaseMetaData = factory.newDatabaseMetaData(this); // We do not need this at the beginning, as we directly query mondrian // in get Databases // The deferred list allows us to only populate the database if needed. /* * What is done here: * * The XMLA interface is queried with discover datasources The current * context only contains the databasemetadata Each row of the result is * wrapped into an object and put into a list */ this.olapDatabases = new DeferredNamedListImpl<Olap4ldDatabase>( Olap4ldConnection.MetadataRequest.DISCOVER_DATASOURCES, new Olap4ldConnection.Context(this, this.olap4jDatabaseMetaData, null, null, null, null, null, null), // With the database handler, we create a database object. This // is the wrapper // Julian has told me about new Olap4ldConnection.DatabaseHandler(), null); Database pop = olapDatabases.get(0); } private void initLinkedDataEngine() throws OlapException { // This is the SPARQL engine // Depending on the databaseName, we create a different Linked Data // Engine if (databaseName.equals("QCRUMB")) { myLinkedData = new OpenVirtuosoEngine(serverUrlObject, datastructuredefinitions, datasets, databaseName); } if (databaseName.equals("OPENVIRTUOSO")) { myLinkedData = new OpenVirtuosoEngine(serverUrlObject, datastructuredefinitions, datasets, databaseName); } if (databaseName.equals("OPENVIRTUOSORULESET")) { myLinkedData = new OpenVirtuosoEngine(serverUrlObject, datastructuredefinitions, datasets, databaseName); } if (databaseName.equals("SESAME")) { myLinkedData = new SesameEngine(serverUrlObject, datastructuredefinitions, datasets, databaseName); } if (databaseName.equals("EMBEDDEDSESAME")) { myLinkedData = new EmbeddedSesameEngine(serverUrlObject, datastructuredefinitions, datasets, databaseName); } } /** * Returns the error-handler * * @return Error-handler */ private LdHelper getHelper() { return helper; } /** * Initializes a cache object and configures it if cache parameters were * specified in the jdbc url. * * @param map * The parameters from the jdbc url. * @throws OlapException * Thrown when there is an error encountered while creating the * cache. */ private void initSoapCache(Map<String, String> map) throws OlapException { // Test if a SOAP cache class was defined if (map.containsKey(Olap4ldDriver.Property.CACHE.name().toUpperCase())) { // Create a properties object to pass to the proxy // so it can configure it's cache Map<String, String> props = new HashMap<String, String>(); // Iterate over map entries to find those related to // the cache config for (Entry<String, String> entry : map.entrySet()) { // Check if the current entry relates to cache config. if (entry.getKey().startsWith( Olap4ldDriver.Property.CACHE.name() + ".")) { props.put( entry.getKey().substring( Olap4ldDriver.Property.CACHE.name() .length() + 1), entry.getValue()); } } // Init the cache ((XmlaOlap4jCachedProxy) this.proxy).setCache(map, props); } } static Map<String, String> parseConnectString(String url, Properties info) { String x = url.substring(CONNECT_STRING_PREFIX.length()); Map<String, String> map = ConnectStringParser.parseConnectString(x); for (Map.Entry<String, String> entry : toMap(info).entrySet()) { map.put(entry.getKey(), entry.getValue()); } return map; } static boolean acceptsURL(String url) { return url.startsWith(CONNECT_STRING_PREFIX); } /** * Creates statement */ public OlapStatement createStatement() { /* * TODO: Here, we need to create the XML (maybe even not) and fill the * OLAP database * * We can even get additional information to be able to fill the table */ return new Olap4ldStatement(this); /* * Instead of parsing the mdx by ourselves and creating sparql queries, * which might be useful later, we "manifest" the current schema in * Mondrian and allow queries there. * * On the one hand, we need to create a proper XML file that the * mondrian server connects to. On the other hand, we need to fill in * the MySQL database. */ // File xslFile = new File( // "C:/Users/b-kaempgen/Documents/Workspaces/Eclipse_SLD/OLAP4LD/resources/xml2nx.xsl"); // // try { // return myOlapServer.getOlapConnection().createStatement(); // } catch (OlapException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // return null; } public PreparedStatement prepareStatement(String sql) throws SQLException { throw new UnsupportedOperationException(); } public CallableStatement prepareCall(String sql) throws SQLException { throw new UnsupportedOperationException(); } public String nativeSQL(String sql) throws SQLException { throw new UnsupportedOperationException(); } public void setAutoCommit(boolean autoCommit) throws SQLException { this.autoCommit = autoCommit; } public boolean getAutoCommit() throws SQLException { return autoCommit; } public void commit() throws SQLException { throw new UnsupportedOperationException(); } /** * Here, I empty the linked data store. */ public void rollback() throws SQLException { // throw new UnsupportedOperationException(); this.myLinkedData.rollback(); } public void close() throws SQLException { closed = true; // For me this means we also have to rollback first rollback(); } public boolean isClosed() throws SQLException { return closed; } public OlapDatabaseMetaData getMetaData() { return olap4jDatabaseMetaData; } public void setReadOnly(boolean readOnly) throws SQLException { this.readOnly = readOnly; } public boolean isReadOnly() throws SQLException { return readOnly; } public void setDatabase(String databaseName) throws OlapException { if (databaseName == null) { throw new OlapException("Database name cannot be null."); } this.olap4jDatabase = (Olap4ldDatabase) getOlapDatabases().get( databaseName); if (this.olap4jDatabase == null) { throw new OlapException("No database named " + databaseName + " could be found."); } this.databaseName = databaseName; this.olap4jCatalog = null; this.olap4jSchema = null; } public String getDatabase() throws OlapException { return getOlapDatabase().getName(); } public Database getOlapDatabase() throws OlapException { if (this.olap4jDatabase == null) { if (this.databaseName == null) { List<Database> databases = getOlapDatabases(); if (databases.size() == 0) { throw new OlapException("No database found."); } this.olap4jDatabase = (Olap4ldDatabase) databases.get(0); this.databaseName = this.olap4jDatabase.getName(); this.olap4jCatalog = null; this.olap4jSchema = null; } else { this.olap4jDatabase = (Olap4ldDatabase) getOlapDatabases().get( this.databaseName); this.olap4jCatalog = null; this.olap4jSchema = null; if (this.olap4jDatabase == null) { throw new OlapException("No database named " + this.databaseName + " could be found."); } } } return olap4jDatabase; } public NamedList<Database> getOlapDatabases() throws OlapException { // TODO: Here, I use myOlapServer // Maybe I should rather return XmlaOlap4jDatabase // return Olap4jUtil.cast(this.myOlapServer.getOlapConnection() // .getOlapDatabases()); Olap4ldUtil._log.config("Metadata object getOlapDatabases()..."); return Olap4jUtil.cast(this.olapDatabases); } /** * Note, we set back connection when we set the catalog. */ public void setCatalog(String catalogName) throws OlapException { if (catalogName == null) { throw new OlapException("Catalog name cannot be null."); } this.olap4jCatalog = (Olap4ldCatalog) getOlapCatalogs() .get(catalogName); if (this.olap4jCatalog == null) { throw new OlapException("No catalog named " + catalogName + " could be found."); } this.catalogName = catalogName; this.olap4jSchema = null; // We don't do any rollbacks since we assume our LD-Engine dynamically adapts to the queries. // try { // Olap4ldUtil._log.info("rollback OlapConnection..."); // rollback(); // } catch (SQLException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } } public String getCatalog() throws OlapException { return getOlapCatalog().getName(); } public Catalog getOlapCatalog() throws OlapException { if (this.olap4jCatalog == null) { final Database database = getOlapDatabase(); if (this.catalogName == null) { if (database.getCatalogs().size() == 0) { throw new OlapException("No catalogs could be found."); } this.olap4jCatalog = (Olap4ldCatalog) database.getCatalogs() .get(0); this.catalogName = this.olap4jCatalog.getName(); this.olap4jSchema = null; } else { this.olap4jCatalog = (Olap4ldCatalog) database.getCatalogs() .get(this.catalogName); if (this.olap4jCatalog == null) { throw new OlapException("No catalog named " + this.catalogName + " could be found."); } this.olap4jSchema = null; } } return olap4jCatalog; } public NamedList<Catalog> getOlapCatalogs() throws OlapException { return getOlapDatabase().getCatalogs(); } public String getSchema() throws OlapException { return getOlapSchema().getName(); } /** * Note, we set back connection when we set the catalog. */ public void setSchema(String schemaName) throws OlapException { if (schemaName == null) { throw new OlapException("Schema name cannot be null."); } final Catalog catalog = getOlapCatalog(); this.olap4jSchema = (Olap4ldSchema) catalog.getSchemas() .get(schemaName); if (this.olap4jSchema == null) { throw new OlapException("No schema named " + schemaName + " could be found in catalog " + catalog.getName()); } this.schemaName = schemaName; // We do not do rollbacks anymore since we assume the LD-Engine organises updates dynamically // try { // Olap4ldUtil._log.info("rollback OlapConnection..."); // rollback(); // } catch (SQLException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } } public synchronized Schema getOlapSchema() throws OlapException { if (this.olap4jSchema == null) { final Catalog catalog = getOlapCatalog(); if (this.schemaName == null) { if (catalog.getSchemas().size() == 0) { throw new OlapException("No schemas could be found."); } this.olap4jSchema = (Olap4ldSchema) catalog.getSchemas().get(0); } else { this.olap4jSchema = (Olap4ldSchema) catalog.getSchemas().get( this.schemaName); if (this.olap4jSchema == null) { throw new OlapException("No schema named " + this.schemaName + " could be found."); } } } return olap4jSchema; } public NamedList<Schema> getOlapSchemas() throws OlapException { return getOlapCatalog().getSchemas(); } public void setTransactionIsolation(int level) throws SQLException { throw new UnsupportedOperationException(); } public int getTransactionIsolation() throws SQLException { return TRANSACTION_NONE; } public SQLWarning getWarnings() throws SQLException { throw new UnsupportedOperationException(); } public void clearWarnings() throws SQLException { // this driver does not support warnings, so nothing to do } public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { throw new UnsupportedOperationException(); } public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { throw new UnsupportedOperationException(); } public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { throw new UnsupportedOperationException(); } public Map<String, Class<?>> getTypeMap() throws SQLException { throw new UnsupportedOperationException(); } public void setTypeMap(Map<String, Class<?>> map) throws SQLException { throw new UnsupportedOperationException(); } public void setHoldability(int holdability) throws SQLException { throw new UnsupportedOperationException(); } public int getHoldability() throws SQLException { throw new UnsupportedOperationException(); } public Savepoint setSavepoint() throws SQLException { throw new UnsupportedOperationException(); } public Savepoint setSavepoint(String name) throws SQLException { throw new UnsupportedOperationException(); } public void rollback(Savepoint savepoint) throws SQLException { throw new UnsupportedOperationException(); } public void releaseSavepoint(Savepoint savepoint) throws SQLException { throw new UnsupportedOperationException(); } public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { throw new UnsupportedOperationException(); } public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { throw new UnsupportedOperationException(); } public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { throw new UnsupportedOperationException(); } public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { throw new UnsupportedOperationException(); } public PreparedStatement prepareStatement(String sql, int columnIndexes[]) throws SQLException { throw new UnsupportedOperationException(); } public PreparedStatement prepareStatement(String sql, String columnNames[]) throws SQLException { throw new UnsupportedOperationException(); } // implement Wrapper public <T> T unwrap(Class<T> iface) throws SQLException { if (iface.isInstance(this)) { return iface.cast(this); } throw getHelper().createException("does not implement '" + iface + "'"); } public boolean isWrapperFor(Class<?> iface) throws SQLException { return iface.isInstance(this); } // implement OlapConnection public PreparedOlapStatement prepareOlapStatement(String mdx) throws OlapException { return factory.newPreparedStatement(mdx, this); } public MdxParserFactory getParserFactory() { return new MdxParserFactory() { public MdxParser createMdxParser(OlapConnection connection) { return new DefaultMdxParserImpl(); } public MdxValidator createMdxValidator(OlapConnection connection) { return new XmlaOlap4jMdxValidator(connection); } }; } public static Map<String, String> toMap(final Properties properties) { return new AbstractMap<String, String>() { public Set<Entry<String, String>> entrySet() { return Olap4jUtil.cast(properties.entrySet()); } }; } /** * Returns the URL which was used to create this connection. * * @return URL */ String getURL() { return url; // throw Olap4jUtil.needToImplement(this); } public void setLocale(Locale locale) { if (locale == null) { throw new IllegalArgumentException("locale must not be null"); } final Locale previousLocale = this.locale; this.locale = locale; // If locale has changed, clear the cache. This is necessary because // metadata elements (e.g. Cubes) only store the caption & description // of the current locale. The SOAP cache, if enabled, will speed things // up a little if a client JVM uses connections to the same server with // different locales. if (!Olap4jUtil.equal(previousLocale, locale)) { clearCache(); } } /** * Clears the cache. */ private void clearCache() { // clearCache probably does not work // TODO: remove or improve logging Olap4ldUtil._log.warning("Warning: Caching olapDatabases... does this work?"); ((DeferredNamedListImpl) this.olapDatabases).reset(); this.olap4jCatalog = null; this.olap4jDatabase = null; this.olap4jSchema = null; } public Locale getLocale() { if (locale == null) { return Locale.getDefault(); } return locale; } public void setRoleName(String roleName) throws OlapException { this.roleName = roleName; } public String getRoleName() { return roleName; } public List<String> getAvailableRoleNames() { // List of available roles is not known. Could potentially add an XMLA // call to populate this list if useful to a client. return null; } public Scenario createScenario() { throw new UnsupportedOperationException(); } public void setScenario(Scenario scenario) { throw new UnsupportedOperationException(); } public Scenario getScenario() { throw new UnsupportedOperationException(); } /** * Encodes a string for use in an XML CDATA section. * * @param value * Value to be xml encoded * @param buf * Buffer to append to */ private static void xmlEncode(StringBuilder buf, String value) { final int n = value.length(); for (int i = 0; i < n; ++i) { char c = value.charAt(i); switch (c) { case '&': buf.append("&"); break; case '<': buf.append("<"); break; case '>': buf.append(">"); break; case '"': buf.append("""); break; case '\'': buf.append("'"); break; default: buf.append(c); } } } // ~ inner classes -------------------------------------------------------- static class DatabaseHandler extends HandlerImpl<Olap4ldDatabase> { @Override public void handle(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields, Context context, List<Olap4ldDatabase> list) throws OlapException { // I want to ask first for the field names String dsName = row[mapFields.get("?DATA_SOURCE_NAME")].toString(); String dsDesc = row[mapFields.get("?DATA_SOURCE_DESCRIPTION")] .toString(); String url = row[mapFields.get("?URL")].toString(); String dsInfo = row[mapFields.get("?DATA_SOURCE_INFO")].toString(); String providerName = row[mapFields.get("?PROVIDER_NAME")] .toString(); // Simply empty List<ProviderType> pTypesList = new ArrayList<ProviderType>(); // Empty List<AuthenticationMode> aModesList = new ArrayList<AuthenticationMode>(); list.add(new Olap4ldDatabase(context.olap4jConnection, dsName, dsDesc, providerName, url, dsInfo, pTypesList, aModesList)); } } static class CatalogHandler extends HandlerImpl<Olap4ldCatalog> { private final Olap4ldDatabase database; public CatalogHandler(Olap4ldDatabase database) { this.database = database; } @Override public void handle(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields, Context context, List<Olap4ldCatalog> list) throws OlapException { // TODO Auto-generated method stub /* * Example: * * <row> <CATALOG_NAME>FoodMart</CATALOG_NAME> <DESCRIPTION>No * description available</DESCRIPTION> <ROLES>California manager,No * HR Cube</ROLES> </row> */ String catalogName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?TABLE_CAT")]); // Unused: DESCRIPTION, ROLES list.add(new Olap4ldCatalog(context.olap4jDatabaseMetaData, database, catalogName)); } } static class CubeHandler extends HandlerImpl<Olap4ldCube> { public void handle(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields, Context context, List<Olap4ldCube> list) throws OlapException { // We need to make the cubeName MDX compatible String cubeName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?CUBE_NAME")]); String caption = Olap4ldLinkedDataUtil.makeCaption( row[mapFields.get("?CUBE_CAPTION")].toString(), row[mapFields.get("?CUBE_NAME")].toString()); String description = row[mapFields.get("?DESCRIPTION")].toString(); list.add(new Olap4ldCube(context.olap4jSchema, cubeName, caption, description)); } } static class DimensionHandler extends HandlerImpl<Olap4ldDimension> { private final Olap4ldCube cubeForCallback; public DimensionHandler(Olap4ldCube cube) { this.cubeForCallback = cube; } public void handle(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields, Context context, List<Olap4ldDimension> list) { // This typically should be a uri so that we need to make it MDX // ready String dimensionName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?DIMENSION_NAME")]); // This should always be a URI String dimensionUniqueName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields .get("?DIMENSION_UNIQUE_NAME")]); String dimensionCaption = Olap4ldLinkedDataUtil.makeCaption( row[mapFields.get("?DIMENSION_CAPTION")].toString(), row[mapFields.get("?DIMENSION_UNIQUE_NAME")].toString()); String description = row[mapFields.get("?DESCRIPTION")].toString(); // From this result only integer values should be returned (can we // actually do this with SPARQL? // TODO: SO far, only unkown(0) is returned, here. final int dimensionType = new Integer( row[mapFields.get("?DIMENSION_TYPE")].toString()); final Dimension.Type type = Dimension.Type.getDictionary() .forOrdinal(dimensionType); // the max of hierarchies // final String defaultHierarchyUniqueName = row[mapFields // .get("?DEFAULT_HIERARCHY")].toString(); // TODO: For default hierarchy it is similar... final String defaultHierarchyUniqueName = null; // simply 0 is returned here final Integer dimensionOrdinal = new Integer( row[mapFields.get("?DIMENSION_ORDINAL")].toString()); Olap4ldDimension dimension = new Olap4ldDimension( context.olap4jCube, dimensionUniqueName, dimensionName, dimensionCaption, description, type, defaultHierarchyUniqueName, dimensionOrdinal == null ? 0 : dimensionOrdinal); list.add(dimension); if (dimensionOrdinal != null) { Collections.sort(list, new Comparator<Olap4ldDimension>() { public int compare(Olap4ldDimension d1, Olap4ldDimension d2) { if (d1.getOrdinal() == d2.getOrdinal()) { return 0; } else if (d1.getOrdinal() > d2.getOrdinal()) { return 1; } else { return -1; } } }); } this.cubeForCallback.dimensionsByUname.put( dimension.getUniqueName(), dimension); } } static class HierarchyHandler extends HandlerImpl<Olap4ldHierarchy> { private final Olap4ldCube cubeForCallback; public HierarchyHandler(Olap4ldCube cubeForCallback) { this.cubeForCallback = cubeForCallback; } public void handle(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields, Context context, List<Olap4ldHierarchy> list) throws OlapException { /* * Example: * * <row> <CATALOG_NAME>FoodMart</CATALOG_NAME> * <SCHEMA_NAME>FoodMart</SCHEMA_NAME> <CUBE_NAME>Sales</CUBE_NAME> * <DIMENSION_UNIQUE_NAME>[Customers]</DIMENSION_UNIQUE_NAME> * <HIERARCHY_NAME>Customers</HIERARCHY_NAME> * <HIERARCHY_UNIQUE_NAME>[Customers]</HIERARCHY_UNIQUE_NAME> * <HIERARCHY_CAPTION>Customers</HIERARCHY_CAPTION> * <DIMENSION_TYPE>3</DIMENSION_TYPE> * <HIERARCHY_CARDINALITY>10407</HIERARCHY_CARDINALITY> * <DEFAULT_MEMBER>[Customers].[All Customers]</DEFAULT_MEMBER> * <ALL_MEMBER>[Customers].[All Customers]</ALL_MEMBER> * <DESCRIPTION>Sales Cube - Customers Hierarchy</DESCRIPTION> * <STRUCTURE>0</STRUCTURE> <IS_VIRTUAL>false</IS_VIRTUAL> * <IS_READWRITE>false</IS_READWRITE> * <DIMENSION_UNIQUE_SETTINGS>0</DIMENSION_UNIQUE_SETTINGS> * <DIMENSION_IS_VISIBLE>true</DIMENSION_IS_VISIBLE> * <HIERARCHY_ORDINAL>9</HIERARCHY_ORDINAL> * <DIMENSION_IS_SHARED>true</DIMENSION_IS_SHARED> * <PARENT_CHILD>false</PARENT_CHILD> </row> */ final String hierarchyUniqueName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields .get("?HIERARCHY_UNIQUE_NAME")]); // SAP BW (and LinkedDataEngine) doesn't return a HIERARCHY_NAME // attribute, // so try to use the unique name instead final String hierarchyName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?HIERARCHY_NAME")]) == null ? hierarchyUniqueName : Olap4ldLinkedDataUtil.convertNodeToMDX(row[mapFields .get("?HIERARCHY_NAME")]); String hierarchyCaption = Olap4ldLinkedDataUtil.makeCaption( row[mapFields.get("?HIERARCHY_CAPTION")].toString(), row[mapFields.get("?HIERARCHY_UNIQUE_NAME")].toString()); // Descriptions do not need to be transformed into MDX. final String description = row[mapFields.get("?DESCRIPTION")] .toString(); // TODO: For us, the all member is always null // final String allMember = row[mapFields.get("?ALL_MEMBER")] // .toString(); final String allMember = null; // TODO: Is not set for measure hierarchies // Problem: The aggregation function max for finding a default // member would not work, since it would aggregate to early. For // now, I simply set the default member to null. // final String defaultMemberUniqueName = row[mapFields // .get("?DEFAULT_MEMBER")].toString(); final String defaultMemberUniqueName = null; Olap4ldHierarchy hierarchy = new Olap4ldHierarchy( context.getDimension(row, mapFields), hierarchyUniqueName, hierarchyName, hierarchyCaption, description, allMember != null, defaultMemberUniqueName); list.add(hierarchy); cubeForCallback.hierarchiesByUname.put(hierarchy.getUniqueName(), hierarchy); } } static class LevelHandler extends HandlerImpl<Olap4ldLevel> { public static final int MDLEVEL_TYPE_CALCULATED = 0x0002; private final Olap4ldCube cubeForCallback; public LevelHandler(Olap4ldCube cubeForCallback) { this.cubeForCallback = cubeForCallback; } public void handle(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields, Context context, List<Olap4ldLevel> list) throws OlapException { /* * Example: * * <row> <CATALOG_NAME>FoodMart</CATALOG_NAME> * <SCHEMA_NAME>FoodMart</SCHEMA_NAME> <CUBE_NAME>Sales</CUBE_NAME> * <DIMENSION_UNIQUE_NAME>[Customers]</DIMENSION_UNIQUE_NAME> * <HIERARCHY_UNIQUE_NAME>[Customers]</HIERARCHY_UNIQUE_NAME> * <LEVEL_NAME>(All)</LEVEL_NAME> * <LEVEL_UNIQUE_NAME>[Customers].[(All)]</LEVEL_UNIQUE_NAME> * <LEVEL_CAPTION>(All)</LEVEL_CAPTION> * <LEVEL_NUMBER>0</LEVEL_NUMBER> * <LEVEL_CARDINALITY>1</LEVEL_CARDINALITY> * <LEVEL_TYPE>1</LEVEL_TYPE> * <CUSTOM_ROLLUP_SETTINGS>0</CUSTOM_ROLLUP_SETTINGS> * <LEVEL_UNIQUE_SETTINGS>3</LEVEL_UNIQUE_SETTINGS> * <LEVEL_IS_VISIBLE>true</LEVEL_IS_VISIBLE> <DESCRIPTION>Sales Cube * - Customers Hierarchy - (All) Level</DESCRIPTION> </row> */ final String levelUniqueName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?LEVEL_UNIQUE_NAME")]); // SAP BW doesn't return a HIERARCHY_NAME attribute, // so try to use the unique name instead final String levelName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?LEVEL_NAME")]) == null ? levelUniqueName : Olap4ldLinkedDataUtil.convertNodeToMDX(row[mapFields .get("?LEVEL_NAME")]); String levelCaption = Olap4ldLinkedDataUtil.makeCaption( row[mapFields.get("?LEVEL_CAPTION")].toString(), row[mapFields.get("?LEVEL_UNIQUE_NAME")].toString()); final String description = row[mapFields.get("?DESCRIPTION")] .toString(); // The distance of the level from the root of the hierarchy. Root // level (abstract/implicit all level) is zero (0). // Default is root level. int levelNumber; if (Olap4ldLinkedDataUtil.convertNodeToMDX(row[mapFields .get("?LEVEL_NUMBER")]) != null) { // Since we use depth, we need to reduce the size with 1. levelNumber = new Integer( row[mapFields.get("?LEVEL_NUMBER")].toString()); if (levelNumber < 0) { throw new UnsupportedOperationException( "The levelNumber cannot be negative!"); } } else { throw new UnsupportedOperationException( "A LEVEL_NUMBER should always be given!"); } // Is Hexadecimal therefore little more complicated String level_type = row[mapFields.get("?LEVEL_TYPE")].toString(); final Integer levelTypeCode = Integer.parseInt( level_type.substring(2, level_type.length()), 16); final Level.Type levelType = Level.Type.getDictionary().forOrdinal( levelTypeCode); boolean calculated = (levelTypeCode & MDLEVEL_TYPE_CALCULATED) != 0; final int levelCardinality = new Integer( row[mapFields.get("?LEVEL_CARDINALITY")].toString()); Olap4ldLevel level = new Olap4ldLevel(context.getHierarchy(row, mapFields), levelUniqueName, levelName, levelCaption, description, levelNumber, levelType, calculated, levelCardinality); list.add(level); cubeForCallback.levelsByUname.put(level.getUniqueName(), level); } } static class MeasureHandler extends HandlerImpl<Olap4ldMeasure> { public void sortList(List<Olap4ldMeasure> list) { Collections.sort(list, new Comparator<Olap4ldMeasure>() { public int compare(Olap4ldMeasure o1, Olap4ldMeasure o2) { return o1.getOrdinal() - o2.getOrdinal(); } }); } @Override public void handle(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields, Context context, List<Olap4ldMeasure> list) throws OlapException { /* * Example: <row> <CATALOG_NAME>FoodMart</CATALOG_NAME> * <SCHEMA_NAME>FoodMart</SCHEMA_NAME> <CUBE_NAME>Sales</CUBE_NAME> * <MEASURE_NAME>Profit</MEASURE_NAME> * <MEASURE_UNIQUE_NAME>[Measures].[Profit]</MEASURE_UNIQUE_NAME> * <MEASURE_CAPTION>Profit</MEASURE_CAPTION> * <MEASURE_AGGREGATOR>127</MEASURE_AGGREGATOR> * <DATA_TYPE>130</DATA_TYPE> * <MEASURE_IS_VISIBLE>true</MEASURE_IS_VISIBLE> <DESCRIPTION>Sales * Cube - Profit Member</DESCRIPTION> </row> */ // XXX: Here, we Linked Data Engine actually need to have a certain // number /* * Here, we get a string with the aggregation function, which we * need to translate into an Aggregator. If no aggregation function * is given, we use COUNT. */ Aggregator aggregator = Measure.Aggregator.COUNT; ParseTreeNode expression = null; Measure.Aggregator measureAggregator = aggregator; Node rowAggregator = row[mapFields.get("?MEASURE_AGGREGATOR")]; if (rowAggregator.toString().toLowerCase() .equals("http://purl.org/olap#sum")) { measureAggregator = Measure.Aggregator.SUM; } else if (rowAggregator.toString().toLowerCase() .equals("http://purl.org/olap#avg")) { measureAggregator = Measure.Aggregator.AVG; } else if (rowAggregator.toString().toLowerCase() .equals("http://purl.org/olap#max")) { measureAggregator = Measure.Aggregator.MIN; } else if (rowAggregator.toString().toLowerCase() .equals("http://purl.org/olap#min")) { measureAggregator = Measure.Aggregator.MAX; } else if (rowAggregator.toString().toLowerCase() .equals("http://purl.org/olap#count")) { measureAggregator = Measure.Aggregator.COUNT; } else if (rowAggregator.toString().toLowerCase() .equals("http://purl.org/olap#calculated")) { measureAggregator = Measure.Aggregator.CALCULATED; // In this case, we should also have an expression. // expression = // LdOlap4jUtil.convertNodeToMDX(row[mapFields.get("?EXPRESSION")]); Olap4ldUtil._log .warning("Warning: Calculated members from the DSD are not supported, yet."); } // Names final String measureName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?MEASURE_NAME")]); final String measureUniqueName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?MEASURE_UNIQUE_NAME")]); // Measure caption will contain the measure property // XXX: Does it? We need to make sure that DatabaseMetadata and the // Metadata objects return the same elements. final String measureCaption = Olap4ldLinkedDataUtil.makeCaption( row[mapFields.get("?MEASURE_CAPTION")].toString(), row[mapFields.get("?MEASURE_UNIQUE_NAME")].toString()); /* * Node.toString is not enough. Integer/Boolean also */ final String description = row[mapFields.get("?MEASURE_CAPTION")] .toString(); // XXX: Here, we Linked Data Engine actually need to have a certain // number // Here, for a certain name, we get a datatype (encoded as // rdfs:range of xsd type) // final Datatype datatype; final Datatype ordinalDatatype; String rowdatatype = row[mapFields.get("?DATA_TYPE")].toString(); if (rowdatatype.toString().toLowerCase() .equals("http://www.w3.org/2001/XMLSchema#integer")) { ordinalDatatype = Datatype.INTEGER; } else if (rowAggregator.toString().toLowerCase() .equals("http://www.w3.org/2001/XMLSchema#decimal")) { ordinalDatatype = Datatype.DOUBLE; } else if (rowAggregator.toString().toLowerCase() .equals("http://www.w3.org/2001/XMLSchema#boolean")) { ordinalDatatype = Datatype.BOOLEAN; } else if (rowAggregator.toString().toLowerCase() .equals("http://www.w3.org/2001/XMLSchema#string")) { ordinalDatatype = Datatype.STRING; } else { ordinalDatatype = Datatype.DOUBLE; } // Here, we need a boolean final boolean measureIsVisible = new Boolean( row[mapFields.get("?MEASURE_IS_VISIBLE")].toString()); /* * Apparently, for each single measure in a cube, i need a member in * the same cube And that member should be in a proper level. Thus, * we need to create levels for the measures, as well. */ // TODO: Does this work? /* * TODO: Null is fine because getCube does not use its parameter */ // We are looking for the member representing the measure. We cannot // simply ask the measures dimension, since it queries the measures // and not the members. final Member member = context.getCube(null).getMetadataReader() .lookupMemberByUniqueName(measureUniqueName); if (member == null) { throw new OlapException( "The server failed to resolve a member with the same unique name as a measure named " + measureUniqueName); } list.add(new Olap4ldMeasure((Olap4ldLevel) member.getLevel(), measureUniqueName, measureName, measureCaption, description, null, measureAggregator, expression, ordinalDatatype, measureIsVisible, member.getOrdinal())); } } static class MemberHandler extends HandlerImpl<Olap4ldMember> { /** * Collection of nodes to ignore because they represent standard * built-in properties of Members. */ private static final Set<String> EXCLUDED_PROPERTY_NAMES = new HashSet<String>( Arrays.asList(Property.StandardMemberProperty.CATALOG_NAME .name(), Property.StandardMemberProperty.CUBE_NAME .name(), Property.StandardMemberProperty.DIMENSION_UNIQUE_NAME .name(), Property.StandardMemberProperty.HIERARCHY_UNIQUE_NAME .name(), Property.StandardMemberProperty.LEVEL_UNIQUE_NAME .name(), Property.StandardMemberProperty.PARENT_LEVEL.name(), Property.StandardMemberProperty.PARENT_COUNT.name(), Property.StandardMemberProperty.MEMBER_KEY.name(), Property.StandardMemberProperty.IS_PLACEHOLDERMEMBER .name(), Property.StandardMemberProperty.IS_DATAMEMBER.name(), Property.StandardMemberProperty.LEVEL_NUMBER.name(), Property.StandardMemberProperty.MEMBER_ORDINAL.name(), Property.StandardMemberProperty.MEMBER_UNIQUE_NAME .name(), Property.StandardMemberProperty.MEMBER_NAME.name(), Property.StandardMemberProperty.PARENT_UNIQUE_NAME .name(), Property.StandardMemberProperty.MEMBER_TYPE.name(), Property.StandardMemberProperty.MEMBER_CAPTION.name(), Property.StandardMemberProperty.CHILDREN_CARDINALITY .name(), Property.StandardMemberProperty.DEPTH .name())); /** * Cached value returned by the {@link Member.Type#values} method, which * calls {@link Class#getEnumConstants()} and unfortunately clones an * array every time. */ private static final Member.Type[] MEMBER_TYPE_VALUES = Member.Type .values(); private void addUserDefinedDimensionProperties( org.semanticweb.yars.nx.Node[] row, org.semanticweb.yars.nx.Node[] firstRow, Olap4ldLevel level, Map<Property, Object> map) { // TODO: Started to implement but not finished // //NodeList nodes = row.getChildNodes(); // // for (int i = 0; i < row.length; i++) { // org.semanticweb.yars.nx.Node node = row[i]; // // Here, only // if (EXCLUDED_PROPERTY_NAMES.contains(firstRow[i].toString())) { // continue; // } // for (Property property : level.getProperties()) { // if (property instanceof XmlaOlap4jProperty // && property.getName().equalsIgnoreCase( // node.getLocalName())) { // map.put(property, node.getTextContent()); // } // } // } } @Override public void handle(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields, Context context, List<Olap4ldMember> list) throws OlapException { /* * Example: * * <row> <CATALOG_NAME>FoodMart</CATALOG_NAME> * <SCHEMA_NAME>FoodMart</SCHEMA_NAME> <CUBE_NAME>Sales</CUBE_NAME> * <DIMENSION_UNIQUE_NAME>[Gender]</DIMENSION_UNIQUE_NAME> * <HIERARCHY_UNIQUE_NAME>[Gender]</HIERARCHY_UNIQUE_NAME> * <LEVEL_UNIQUE_NAME>[Gender].[Gender]</LEVEL_UNIQUE_NAME> * <LEVEL_NUMBER>1</LEVEL_NUMBER> <MEMBER_ORDINAL>1</MEMBER_ORDINAL> * <MEMBER_NAME>F</MEMBER_NAME> * <MEMBER_UNIQUE_NAME>[Gender].[F]</MEMBER_UNIQUE_NAME> * <MEMBER_TYPE>1</MEMBER_TYPE> <MEMBER_CAPTION>F</MEMBER_CAPTION> * <CHILDREN_CARDINALITY>0</CHILDREN_CARDINALITY> * <PARENT_LEVEL>0</PARENT_LEVEL> <PARENT_UNIQUE_NAME>[Gender].[All * Gender]</PARENT_UNIQUE_NAME> <PARENT_COUNT>1</PARENT_COUNT> * <DEPTH>1</DEPTH> <!-- mondrian-specific --> </row> */ if (false) { int levelNumber = new Integer( row[mapFields.get("?LEVEL_NUMBER")].toString()); } // TODO: FOr now, we only have member ordinal = 0 int memberOrdinal = 0; String memberUniqueName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?MEMBER_UNIQUE_NAME")]); String memberName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?MEMBER_NAME")]); // Parent unique name should be null if no parent is available. // TODO: Null handling is still not thought through since LDEngine // returns <null> String parentUniqueName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?PARENT_UNIQUE_NAME")]); // We see, an Integer is used, which means we need to return the // type in terms of the number Member.Type memberType = MEMBER_TYPE_VALUES[new Integer( row[mapFields.get("?MEMBER_TYPE")].toString())]; String memberCaption = Olap4ldLinkedDataUtil.makeCaption( row[mapFields.get("?MEMBER_CAPTION")].toString(), row[mapFields.get("?MEMBER_UNIQUE_NAME")].toString()); // TODO: For now, we only have cardinality 0 int childrenCardinality = 0; // According to the olap4j spec, description always returns null. String description = null; // Gather member property values into a temporary map, so we can // create the member with all properties known. XmlaOlap4jMember // uses an ArrayMap for property values and it is not efficient to // add entries to the map one at a time. final Olap4ldLevel level = context.getLevel(row, mapFields); // TODO: For now, I will not have any additional properties final Map<Property, Object> map = new HashMap<Property, Object>(); // addUserDefinedDimensionProperties(row, level, map); // Usually members have the same depth as their level. (Ragged and // parent-child hierarchies are an exception.) Only store depth for // the unusual ones. // final Integer depth = integerElement(row, // Property.StandardMemberProperty.DEPTH.name()); // if (depth != null && depth.intValue() != level.getDepth()) { // map.put(Property.StandardMemberProperty.DEPTH, depth); // } // If this member is a measure, we want to return an object that // implements the Measure interface to all API calls. But we also // need to retrieve the properties that occur in MDSCHEMA_MEMBERS // that are not available in MDSCHEMA_MEASURES, so we create a // member for internal use. // TODO: Expressions are not supported, yet, for members. Olap4ldMember member = new Olap4ldMember(level, memberUniqueName, memberName, memberCaption, description, parentUniqueName, memberType, childrenCardinality, memberOrdinal, map, null); list.add(member); } } static class NamedSetHandler extends HandlerImpl<Olap4ldNamedSet> { public void handle(Element row, Context context, List<Olap4ldNamedSet> list) { /* * Example: * * <row> <CATALOG_NAME>FoodMart</CATALOG_NAME> * <SCHEMA_NAME>FoodMart</SCHEMA_NAME> * <CUBE_NAME>Warehouse</CUBE_NAME> <SET_NAME>[Top * Sellers]</SET_NAME> <SCOPE>1</SCOPE> </row> */ final String setName = Olap4ldUtil.stringElement(row, "SET_NAME"); list.add(new Olap4ldNamedSet(context.getCube(row), setName)); } @Override public void handle(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields, Context context, List<Olap4ldNamedSet> list) throws OlapException { // TODO Auto-generated method stub } } static class SchemaHandler extends HandlerImpl<Olap4ldSchema> { public void handle(Element row, Context context, List<Olap4ldSchema> list) throws OlapException { /* * Example: * * <row> <CATALOG_NAME>LOCALDB</CATLAOG_NAME> * <SCHEMA_NAME>FoodMart</SCHEMA_NAME> * <SCHEMA_OWNER>dbo</SCHEMA_OWNER> </row> */ String schemaName = Olap4ldUtil.stringElement(row, "TABLE_SCHEM"); list.add(new Olap4ldSchema(context.getCatalog(row), (schemaName == null) ? "" : schemaName)); } public void handle(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields, Context context, List<Olap4ldSchema> list) throws OlapException { /* * Example: * * <row> <CATALOG_NAME>LOCALDB</CATLAOG_NAME> * <SCHEMA_NAME>FoodMart</SCHEMA_NAME> * <SCHEMA_OWNER>dbo</SCHEMA_OWNER> </row> */ String schemaName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?SCHEMA_NAME")]); list.add(new Olap4ldSchema(context.getCatalog(row, mapFields), (schemaName == null) ? "" : schemaName)); } } static class CatalogSchemaHandler extends HandlerImpl<Olap4ldSchema> { private String catalogName; public CatalogSchemaHandler(String catalogName) { super(); if (catalogName == null) { throw new RuntimeException( "The CatalogSchemaHandler handler requires a catalog " + "name."); } this.catalogName = catalogName; } public void handle(Element row, Context context, List<Olap4ldSchema> list) throws OlapException { /* * Example: * * <row> <CATALOG_NAME>CatalogName</CATLAOG_NAME> * <SCHEMA_NAME>FoodMart</SCHEMA_NAME> * <SCHEMA_OWNER>dbo</SCHEMA_OWNER> </row> */ // We are looking for a schema name from the cubes query restricted // on the catalog name. Some servers don't support nor include the // SCHEMA_NAME column in its response. If it's null, we convert it // to an empty string as to not cause problems later on. final String schemaName = Olap4ldUtil.stringElement(row, "TABLE_SCHEM"); final String catalogName = Olap4ldUtil.stringElement(row, "TABLE_CATALOG"); final String schemaName2 = (schemaName == null) ? "" : schemaName; if (this.catalogName.equals(catalogName) && ((NamedList<Olap4ldSchema>) list).get(schemaName2) == null) { list.add(new Olap4ldSchema(context.getCatalog(row), schemaName2)); } } /** * We do not need this, as we create schemata normally. */ public void handle(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields, Context context, List<Olap4ldSchema> list) throws OlapException { /* * Example: * * <row> <CATALOG_NAME>CatalogName</CATLAOG_NAME> * <SCHEMA_NAME>FoodMart</SCHEMA_NAME> * <SCHEMA_OWNER>dbo</SCHEMA_OWNER> </row> */ // We are looking for a schema name from the cubes query restricted // on the catalog name. Some servers don't support nor include the // SCHEMA_NAME column in its response. If it's null, we convert it // to an empty string as to not cause problems later on. final String schemaName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?TABLE_SCHEM")]); final String catalogName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?TABLE_CAT")]); final String schemaName2 = (schemaName == null) ? "" : schemaName; if (this.catalogName.equals(catalogName) && ((NamedList<Olap4ldSchema>) list).get(schemaName2) == null) { list.add(new Olap4ldSchema(context.getCatalog(row, mapFields), schemaName2)); } } } static class PropertyHandler extends HandlerImpl<Olap4ldProperty> { public void handle(Element row, Context context, List<Olap4ldProperty> list) throws OlapException { /* * Example: * * <row> <CATALOG_NAME>FoodMart</CATALOG_NAME> * <SCHEMA_NAME>FoodMart</SCHEMA_NAME> <CUBE_NAME>HR</CUBE_NAME> * <DIMENSION_UNIQUE_NAME>[Store]</DIMENSION_UNIQUE_NAME> * <HIERARCHY_UNIQUE_NAME>[Store]</HIERARCHY_UNIQUE_NAME> * <LEVEL_UNIQUE_NAME>[Store].[Store Name]</LEVEL_UNIQUE_NAME> * <PROPERTY_NAME>Store Manager</PROPERTY_NAME> * <PROPERTY_CAPTION>Store Manager</PROPERTY_CAPTION> * <PROPERTY_TYPE>1</PROPERTY_TYPE> <DATA_TYPE>130</DATA_TYPE> * <PROPERTY_CONTENT_TYPE>0</PROPERTY_CONTENT_TYPE> <DESCRIPTION>HR * Cube - Store Hierarchy - Store Name Level - Store Manager * Property</DESCRIPTION> </row> */ String description = Olap4ldUtil.stringElement(row, "DESCRIPTION"); String uniqueName = Olap4ldUtil.stringElement(row, "DESCRIPTION"); String caption = Olap4ldUtil.stringElement(row, "PROPERTY_CAPTION"); String name = Olap4ldUtil.stringElement(row, "PROPERTY_NAME"); Datatype datatype; Datatype ordinalDatatype = Datatype.getDictionary().forName( Olap4ldUtil.stringElement(row, "DATA_TYPE")); if (ordinalDatatype == null) { datatype = Datatype.getDictionary().forOrdinal( Olap4ldUtil.integerElement(row, "DATA_TYPE")); } else { datatype = ordinalDatatype; } final Integer contentTypeOrdinal = Olap4ldUtil.integerElement(row, "PROPERTY_CONTENT_TYPE"); Property.ContentType contentType = contentTypeOrdinal == null ? null : Property.ContentType.getDictionary().forOrdinal( contentTypeOrdinal); int propertyType = Olap4ldUtil.integerElement(row, "PROPERTY_TYPE"); Set<Property.TypeFlag> type = Property.TypeFlag.getDictionary() .forMask(propertyType); list.add(new Olap4ldProperty(uniqueName, name, caption, description, datatype, type, contentType)); } @Override public void handle(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields, Context context, List<Olap4ldProperty> list) throws OlapException { // TODO Auto-generated method stub } } /** * Callback for converting LD results into metadata elements. */ interface Handler<T extends Named> { /** * Converts an NX element from an LD result set into a metadata element * and appends it to a list of metadata elements. * * @param row * NX element * * @param context * Context (schema, cube, dimension, etc.) that the request * was executed in and that the element will belong to * * @param list * List of metadata elements to append new metadata element * * @throws OlapException * on error */ void handle(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields, Context context, List<T> list) throws OlapException; /** * Sorts a list of metadata elements. * * <p> * For most element types, the order returned by XMLA is correct, and * this method will no-op. * * @param list * List of metadata elements */ void sortList(List<T> list); } static abstract class HandlerImpl<T extends Named> implements Handler<T> { public void sortList(List<T> list) { // do nothing - assume XMLA returned list in correct order } } static class Context { final Olap4ldConnection olap4jConnection; final Olap4ldDatabaseMetaData olap4jDatabaseMetaData; final Olap4ldCatalog olap4jCatalog; final Olap4ldSchema olap4jSchema; final Olap4ldCube olap4jCube; final Olap4ldDimension olap4jDimension; final Olap4ldHierarchy olap4jHierarchy; final Olap4ldLevel olap4jLevel; /** * Creates a Context. * * The context is re-written in order to deal with NXNodes. * * @param olap4jConnection * Connection (must not be null) * @param olap4jDatabaseMetaData * DatabaseMetaData (may be null) * @param olap4jCatalog * Catalog (may be null if DatabaseMetaData is null) * @param olap4jSchema * Schema (may be null if Catalog is null) * @param olap4jCube * Cube (may be null if Schema is null) * @param olap4jDimension * Dimension (may be null if Cube is null) * @param olap4jHierarchy * Hierarchy (may be null if Dimension is null) * @param olap4jLevel * Level (may be null if Hierarchy is null) */ Context(Olap4ldConnection olap4jConnection, Olap4ldDatabaseMetaData olap4jDatabaseMetaData, Olap4ldCatalog olap4jCatalog, Olap4ldSchema olap4jSchema, Olap4ldCube olap4jCube, Olap4ldDimension olap4jDimension, Olap4ldHierarchy olap4jHierarchy, Olap4ldLevel olap4jLevel) { this.olap4jConnection = olap4jConnection; this.olap4jDatabaseMetaData = olap4jDatabaseMetaData; this.olap4jCatalog = olap4jCatalog; this.olap4jSchema = olap4jSchema; this.olap4jCube = olap4jCube; this.olap4jDimension = olap4jDimension; this.olap4jHierarchy = olap4jHierarchy; this.olap4jLevel = olap4jLevel; assert (olap4jDatabaseMetaData != null || olap4jCatalog == null) && (olap4jCatalog != null || olap4jSchema == null) && (olap4jSchema != null || olap4jCube == null) && (olap4jCube != null || olap4jDimension == null) && (olap4jDimension != null || olap4jHierarchy == null) && (olap4jHierarchy != null || olap4jLevel == null); } /** * Shorthand way to create a Context at Cube level or finer. * * @param olap4jCube * Cube (must not be null) * @param olap4jDimension * Dimension (may be null) * @param olap4jHierarchy * Hierarchy (may be null if Dimension is null) * @param olap4jLevel * Level (may be null if Hierarchy is null) */ Context(Olap4ldCube olap4jCube, Olap4ldDimension olap4jDimension, Olap4ldHierarchy olap4jHierarchy, Olap4ldLevel olap4jLevel) { this( olap4jCube.olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection, olap4jCube.olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData, olap4jCube.olap4jSchema.olap4jCatalog, olap4jCube.olap4jSchema, olap4jCube, olap4jDimension, olap4jHierarchy, olap4jLevel); } /** * Shorthand way to create a Context at Level level. * * @param olap4jLevel * Level (must not be null) */ Context(Olap4ldLevel olap4jLevel) { this(olap4jLevel.olap4jHierarchy.olap4jDimension.olap4jCube, olap4jLevel.olap4jHierarchy.olap4jDimension, olap4jLevel.olap4jHierarchy, olap4jLevel); } Olap4ldHierarchy getHierarchy(Element row) { if (olap4jHierarchy != null) { return olap4jHierarchy; } final String hierarchyUniqueName = Olap4ldUtil.stringElement(row, "HIERARCHY_UNIQUE_NAME"); Olap4ldHierarchy hierarchy = getCube(row).hierarchiesByUname .get(hierarchyUniqueName); if (hierarchy == null) { // Apparently, the code has requested a member that is // not queried for yet. We must force the initialization // of the dimension tree first. final String dimensionUniqueName = Olap4ldUtil.stringElement( row, "DIMENSION_UNIQUE_NAME"); String dimensionName = Olap4jUtil.parseUniqueName( dimensionUniqueName).get(0); Olap4ldDimension dimension = getCube(row).dimensions .get(dimensionName); dimension.getHierarchies().size(); // Now we attempt to resolve again hierarchy = getCube(row).hierarchiesByUname .get(hierarchyUniqueName); } return hierarchy; } Olap4ldHierarchy getHierarchy(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields) { if (olap4jHierarchy != null) { return olap4jHierarchy; } final String hierarchyUniqueName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields .get("?HIERARCHY_UNIQUE_NAME")]); Olap4ldHierarchy hierarchy = getCube(row, mapFields).hierarchiesByUname .get(hierarchyUniqueName); if (hierarchy == null) { // Apparently, the code has requested a member that is // not queried for yet. We must force the initialization // of the dimension tree first. final String dimensionUniqueName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields .get("?DIMENSION_UNIQUE_NAME")]); // TODO: For a specific cube, we need the local name of the // dimension, which in our case however always is the unique // name, so far. // String dimensionName = Olap4jUtil.parseUniqueName( // dimensionUniqueName).get(0); String dimensionName = dimensionUniqueName; Olap4ldDimension dimension = getCube(row, mapFields).dimensions .get(dimensionName); dimension.getHierarchies().size(); // Now we attempt to resolve again hierarchy = getCube(row, mapFields).hierarchiesByUname .get(hierarchyUniqueName); } return hierarchy; } Olap4ldCube getCube(Element row) { if (olap4jCube != null) { return olap4jCube; } throw new UnsupportedOperationException(); // todo: } Olap4ldCube getCube(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields) { if (olap4jCube != null) { return olap4jCube; } throw new UnsupportedOperationException(); // todo: } Olap4ldDimension getDimension(Element row) { if (olap4jDimension != null) { return olap4jDimension; } final String dimensionUniqueName = Olap4ldUtil.stringElement(row, "DIMENSION_UNIQUE_NAME"); Olap4ldDimension dimension = getCube(row).dimensionsByUname .get(dimensionUniqueName); // Apparently, the code has requested a member that is // not queried for yet. if (dimension == null) { final String dimensionName = Olap4ldUtil.stringElement(row, "DIMENSION_NAME"); return getCube(row).dimensions.get(dimensionName); } return dimension; } Olap4ldDimension getDimension(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields) { if (olap4jDimension != null) { return olap4jDimension; } final String dimensionUniqueName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields .get("?DIMENSION_UNIQUE_NAME")]); Olap4ldDimension dimension = getCube(row, mapFields).dimensionsByUname .get(dimensionUniqueName); // Apparently, the code has requested a member that is // not queried for yet. if (dimension == null) { final String dimensionName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?DIMENSION_NAME")]); return getCube(row, mapFields).dimensions.get(dimensionName); } return dimension; } public Olap4ldLevel getLevel(Element row) { if (olap4jLevel != null) { return olap4jLevel; } final String levelUniqueName = Olap4ldUtil.stringElement(row, "LEVEL_UNIQUE_NAME"); Olap4ldLevel level = getCube(row).levelsByUname .get(levelUniqueName); if (level == null) { // Apparently, the code has requested a member that is // not queried for yet. We must force the initialization // of the dimension tree first. final String dimensionUniqueName = Olap4ldUtil.stringElement( row, "DIMENSION_UNIQUE_NAME"); String dimensionName = Olap4jUtil.parseUniqueName( dimensionUniqueName).get(0); Olap4ldDimension dimension = getCube(row).dimensions .get(dimensionName); for (Hierarchy hierarchyInit : dimension.getHierarchies()) { hierarchyInit.getLevels().size(); } // Now we attempt to resolve again level = getCube(row).levelsByUname.get(levelUniqueName); } return level; } public Olap4ldLevel getLevel(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields) { if (olap4jLevel != null) { return olap4jLevel; } final String levelUniqueName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?LEVEL_UNIQUE_NAME")]); Olap4ldLevel level = getCube(row, mapFields).levelsByUname .get(levelUniqueName); if (level == null) { // Apparently, the code has requested a member that is // not queried for yet. We must force the initialization // of the dimension tree first. final String dimensionUniqueName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields .get("?DIMENSION_UNIQUE_NAME")]); // TODO: For a specific cube, we need the local name of the // dimension, which in our case however always is the unique // name, so far. // String dimensionName = Olap4jUtil.parseUniqueName( // dimensionUniqueName).get(0); String dimensionName = dimensionUniqueName; Olap4ldCube cube = getCube(row, mapFields); Olap4ldDimension dimension = cube.dimensions.get(dimensionName); for (Hierarchy hierarchyInit : dimension.getHierarchies()) { hierarchyInit.getLevels().size(); } // Now we attempt to resolve again level = getCube(row, mapFields).levelsByUname .get(levelUniqueName); } return level; } public Olap4ldCatalog getCatalog(Element row) throws OlapException { if (olap4jCatalog != null) { return olap4jCatalog; } final String catalogName = Olap4ldUtil.stringElement(row, "CATALOG_NAME"); return (Olap4ldCatalog) olap4jConnection.getOlapCatalogs().get( catalogName); } public Olap4ldCatalog getCatalog(org.semanticweb.yars.nx.Node[] row, Map<String, Integer> mapFields) throws OlapException { if (olap4jCatalog != null) { return olap4jCatalog; } final String catalogName = Olap4ldLinkedDataUtil .convertNodeToMDX(row[mapFields.get("?CATALOG_NAME")]); return (Olap4ldCatalog) olap4jConnection.getOlapCatalogs().get( catalogName); } } /** * Here, the different methods, XMLA provides are defined. * * @author b-kaempgen * */ enum MetadataRequest { DISCOVER_DATASOURCES(new MetadataColumn("DataSourceName"), new MetadataColumn("DataSourceDescription"), new MetadataColumn("URL"), new MetadataColumn("DataSourceInfo"), new MetadataColumn( "ProviderName"), new MetadataColumn("ProviderType"), new MetadataColumn("AuthenticationMode")), DISCOVER_SCHEMA_ROWSETS( new MetadataColumn("SchemaName"), new MetadataColumn( "SchemaGuid"), new MetadataColumn("Restrictions"), new MetadataColumn("Description")), DISCOVER_ENUMERATORS( new MetadataColumn("EnumName"), new MetadataColumn( "EnumDescription"), new MetadataColumn("EnumType"), new MetadataColumn("ElementName"), new MetadataColumn( "ElementDescription"), new MetadataColumn( "ElementValue")), DISCOVER_PROPERTIES( new MetadataColumn("PropertyName"), new MetadataColumn( "PropertyDescription"), new MetadataColumn( "PropertyType"), new MetadataColumn( "PropertyAccessType"), new MetadataColumn("IsRequired"), new MetadataColumn("Value")), DISCOVER_KEYWORDS( new MetadataColumn("Keyword")), DISCOVER_LITERALS( new MetadataColumn("LiteralName"), new MetadataColumn( "LiteralValue"), new MetadataColumn( "LiteralInvalidChars"), new MetadataColumn( "LiteralInvalidStartingChars"), new MetadataColumn( "LiteralMaxLength")), DBSCHEMA_CATALOGS( new MetadataColumn("CATALOG_NAME"), new MetadataColumn( "DESCRIPTION"), new MetadataColumn("ROLES"), new MetadataColumn("DATE_MODIFIED")), DBSCHEMA_COLUMNS( new MetadataColumn("TABLE_CATALOG"), new MetadataColumn( "TABLE_SCHEMA"), new MetadataColumn("TABLE_NAME"), new MetadataColumn("COLUMN_NAME"), new MetadataColumn( "ORDINAL_POSITION"), new MetadataColumn( "COLUMN_HAS_DEFAULT"), new MetadataColumn( "COLUMN_FLAGS"), new MetadataColumn("IS_NULLABLE"), new MetadataColumn("DATA_TYPE"), new MetadataColumn( "CHARACTER_MAXIMUM_LENGTH"), new MetadataColumn( "CHARACTER_OCTET_LENGTH"), new MetadataColumn( "NUMERIC_PRECISION"), new MetadataColumn( "NUMERIC_SCALE")), DBSCHEMA_PROVIDER_TYPES( new MetadataColumn("TYPE_NAME"), new MetadataColumn("DATA_TYPE"), new MetadataColumn( "COLUMN_SIZE"), new MetadataColumn("LITERAL_PREFIX"), new MetadataColumn("LITERAL_SUFFIX"), new MetadataColumn( "IS_NULLABLE"), new MetadataColumn("CASE_SENSITIVE"), new MetadataColumn("SEARCHABLE"), new MetadataColumn( "UNSIGNED_ATTRIBUTE"), new MetadataColumn( "FIXED_PREC_SCALE"), new MetadataColumn( "AUTO_UNIQUE_VALUE"), new MetadataColumn("IS_LONG"), new MetadataColumn("BEST_MATCH")), DBSCHEMA_TABLES( new MetadataColumn("TABLE_CATALOG"), new MetadataColumn( "TABLE_SCHEMA"), new MetadataColumn("TABLE_NAME"), new MetadataColumn("TABLE_TYPE"), new MetadataColumn( "TABLE_GUID"), new MetadataColumn("DESCRIPTION"), new MetadataColumn("TABLE_PROPID"), new MetadataColumn( "DATE_CREATED"), new MetadataColumn("DATE_MODIFIED")), DBSCHEMA_TABLES_INFO( new MetadataColumn("TABLE_CATALOG"), new MetadataColumn( "TABLE_SCHEMA"), new MetadataColumn("TABLE_NAME"), new MetadataColumn("TABLE_TYPE"), new MetadataColumn( "TABLE_GUID"), new MetadataColumn("BOOKMARKS"), new MetadataColumn("BOOKMARK_TYPE"), new MetadataColumn( "BOOKMARK_DATATYPE"), new MetadataColumn( "BOOKMARK_MAXIMUM_LENGTH"), new MetadataColumn( "BOOKMARK_INFORMATION"), new MetadataColumn( "TABLE_VERSION"), new MetadataColumn("CARDINALITY"), new MetadataColumn("DESCRIPTION"), new MetadataColumn( "TABLE_PROPID")), DBSCHEMA_SCHEMATA(new MetadataColumn( "CATALOG_NAME"), new MetadataColumn("SCHEMA_NAME"), new MetadataColumn("SCHEMA_OWNER")), MDSCHEMA_ACTIONS( new MetadataColumn("CATALOG_NAME"), new MetadataColumn( "SCHEMA_NAME"), new MetadataColumn("CUBE_NAME"), new MetadataColumn("ACTION_NAME"), new MetadataColumn( "COORDINATE"), new MetadataColumn("COORDINATE_TYPE")), MDSCHEMA_CUBES( new MetadataColumn("CATALOG_NAME"), new MetadataColumn( "SCHEMA_NAME"), new MetadataColumn("CUBE_NAME"), new MetadataColumn("CUBE_TYPE"), new MetadataColumn("CUBE_GUID"), new MetadataColumn( "CREATED_ON"), new MetadataColumn("LAST_SCHEMA_UPDATE"), new MetadataColumn( "SCHEMA_UPDATED_BY"), new MetadataColumn( "LAST_DATA_UPDATE"), new MetadataColumn( "DATA_UPDATED_BY"), new MetadataColumn( "IS_DRILLTHROUGH_ENABLED"), new MetadataColumn( "IS_WRITE_ENABLED"), new MetadataColumn("IS_LINKABLE"), new MetadataColumn("IS_SQL_ENABLED"), new MetadataColumn( "DESCRIPTION"), new MetadataColumn("CUBE_CAPTION"), new MetadataColumn("BASE_CUBE_NAME")), MDSCHEMA_DIMENSIONS( new MetadataColumn("CATALOG_NAME"), new MetadataColumn( "SCHEMA_NAME"), new MetadataColumn("CUBE_NAME"), new MetadataColumn("DIMENSION_NAME"), new MetadataColumn( "DIMENSION_UNIQUE_NAME"), new MetadataColumn( "DIMENSION_GUID"), new MetadataColumn( "DIMENSION_CAPTION"), new MetadataColumn( "DIMENSION_ORDINAL"), new MetadataColumn( "DIMENSION_TYPE"), new MetadataColumn( "DIMENSION_CARDINALITY"), new MetadataColumn( "DEFAULT_HIERARCHY"), new MetadataColumn("DESCRIPTION"), new MetadataColumn( "IS_VIRTUAL"), new MetadataColumn("IS_READWRITE"), new MetadataColumn("DIMENSION_UNIQUE_SETTINGS"), new MetadataColumn("DIMENSION_MASTER_UNIQUE_NAME"), new MetadataColumn("DIMENSION_IS_VISIBLE")), MDSCHEMA_FUNCTIONS( new MetadataColumn("FUNCTION_NAME"), new MetadataColumn( "DESCRIPTION"), new MetadataColumn("PARAMETER_LIST"), new MetadataColumn("RETURN_TYPE"), new MetadataColumn("ORIGIN"), new MetadataColumn( "INTERFACE_NAME"), new MetadataColumn("LIBRARY_NAME"), new MetadataColumn("CAPTION")), MDSCHEMA_HIERARCHIES( new MetadataColumn("CATALOG_NAME"), new MetadataColumn( "SCHEMA_NAME"), new MetadataColumn("CUBE_NAME"), new MetadataColumn("DIMENSION_UNIQUE_NAME"), new MetadataColumn("HIERARCHY_NAME"), new MetadataColumn( "HIERARCHY_UNIQUE_NAME"), new MetadataColumn( "HIERARCHY_GUID"), new MetadataColumn( "HIERARCHY_CAPTION"), new MetadataColumn( "DIMENSION_TYPE"), new MetadataColumn( "HIERARCHY_CARDINALITY"), new MetadataColumn( "DEFAULT_MEMBER"), new MetadataColumn("ALL_MEMBER"), new MetadataColumn("DESCRIPTION"), new MetadataColumn( "STRUCTURE"), new MetadataColumn("IS_VIRTUAL"), new MetadataColumn("IS_READWRITE"), new MetadataColumn( "DIMENSION_UNIQUE_SETTINGS"), new MetadataColumn( "DIMENSION_IS_VISIBLE"), new MetadataColumn( "HIERARCHY_IS_VISIBLE"), new MetadataColumn( "HIERARCHY_ORDINAL"), new MetadataColumn( "DIMENSION_IS_SHARED"), new MetadataColumn( "PARENT_CHILD")), MDSCHEMA_LEVELS(new MetadataColumn( "CATALOG_NAME"), new MetadataColumn("SCHEMA_NAME"), new MetadataColumn("CUBE_NAME"), new MetadataColumn( "DIMENSION_UNIQUE_NAME"), new MetadataColumn( "HIERARCHY_UNIQUE_NAME"), new MetadataColumn( "LEVEL_NAME"), new MetadataColumn("LEVEL_UNIQUE_NAME"), new MetadataColumn("LEVEL_GUID"), new MetadataColumn( "LEVEL_CAPTION"), new MetadataColumn("LEVEL_NUMBER"), new MetadataColumn("LEVEL_CARDINALITY"), new MetadataColumn( "LEVEL_TYPE"), new MetadataColumn( "CUSTOM_ROLLUP_SETTINGS"), new MetadataColumn( "LEVEL_UNIQUE_SETTINGS"), new MetadataColumn( "LEVEL_IS_VISIBLE"), new MetadataColumn("DESCRIPTION")), MDSCHEMA_MEASURES( new MetadataColumn("CATALOG_NAME"), new MetadataColumn( "SCHEMA_NAME"), new MetadataColumn("CUBE_NAME"), new MetadataColumn("MEASURE_NAME"), new MetadataColumn( "MEASURE_UNIQUE_NAME"), new MetadataColumn( "MEASURE_CAPTION"), new MetadataColumn("MEASURE_GUID"), new MetadataColumn("MEASURE_AGGREGATOR"), new MetadataColumn( "DATA_TYPE"), new MetadataColumn("MEASURE_IS_VISIBLE"), new MetadataColumn("LEVELS_LIST"), new MetadataColumn( "DESCRIPTION")), MDSCHEMA_MEMBERS(new MetadataColumn( "CATALOG_NAME"), new MetadataColumn("SCHEMA_NAME"), new MetadataColumn("CUBE_NAME"), new MetadataColumn( "DIMENSION_UNIQUE_NAME"), new MetadataColumn( "HIERARCHY_UNIQUE_NAME"), new MetadataColumn( "LEVEL_UNIQUE_NAME"), new MetadataColumn("LEVEL_NUMBER"), new MetadataColumn( "MEMBER_ORDINAL"), new MetadataColumn("MEMBER_NAME"), new MetadataColumn("MEMBER_UNIQUE_NAME"), new MetadataColumn( "MEMBER_TYPE"), new MetadataColumn("MEMBER_GUID"), new MetadataColumn("MEMBER_CAPTION"), new MetadataColumn( "CHILDREN_CARDINALITY"), new MetadataColumn( "PARENT_LEVEL"), new MetadataColumn( "PARENT_UNIQUE_NAME"), new MetadataColumn( "PARENT_COUNT"), new MetadataColumn("TREE_OP"), new MetadataColumn("DEPTH")), MDSCHEMA_PROPERTIES( new MetadataColumn("CATALOG_NAME"), new MetadataColumn( "SCHEMA_NAME"), new MetadataColumn("CUBE_NAME"), new MetadataColumn("DIMENSION_UNIQUE_NAME"), new MetadataColumn("HIERARCHY_UNIQUE_NAME"), new MetadataColumn("LEVEL_UNIQUE_NAME"), new MetadataColumn( "MEMBER_UNIQUE_NAME"), new MetadataColumn( "PROPERTY_NAME"), new MetadataColumn("PROPERTY_CAPTION"), new MetadataColumn( "PROPERTY_TYPE"), new MetadataColumn("DATA_TYPE"), new MetadataColumn("PROPERTY_CONTENT_TYPE"), new MetadataColumn("DESCRIPTION")), MDSCHEMA_SETS( new MetadataColumn("CATALOG_NAME"), new MetadataColumn( "SCHEMA_NAME"), new MetadataColumn("CUBE_NAME"), new MetadataColumn("SET_NAME"), new MetadataColumn("SCOPE")); final List<MetadataColumn> columns; final Map<String, MetadataColumn> columnsByName; final Map<String, Integer> indexByName; /** * Creates a MetadataRequest. * * Note: DBSCHEMA_CATALOGS and DBSCHEMA_SCHEMATA are special w.r.t. the * columns. * * @param columns * Columns */ MetadataRequest(MetadataColumn... columns) { if (name().equals("DBSCHEMA_CATALOGS")) { // DatabaseMetaData.getCatalogs() is defined by JDBC not XMLA, // so has just one column. Ignore the 4 columns from XMLA. columns = new MetadataColumn[] { new MetadataColumn( "CATALOG_NAME", "TABLE_CAT") }; } else if (name().equals("DBSCHEMA_SCHEMATA")) { // DatabaseMetaData.getCatalogs() is defined by JDBC not XMLA, // so has just one column. Ignore the 4 columns from XMLA. columns = new MetadataColumn[] { new MetadataColumn("SCHEMA_NAME", "TABLE_SCHEM"), new MetadataColumn("CATALOG_NAME", "TABLE_CAT") }; } this.columns = UnmodifiableArrayList.asCopyOf(columns); final Map<String, MetadataColumn> map = new HashMap<String, MetadataColumn>(); final Map<String, Integer> indexmap = new HashMap<String, Integer>(); int index = 0; for (MetadataColumn column : columns) { map.put(column.name, column); indexmap.put(column.name, index++); } this.columnsByName = Collections.unmodifiableMap(map); this.indexByName = Collections.unmodifiableMap(indexmap); } /** * Returns whether this request requires a * {@code <DatasourceName>} element. * * @return whether this request requires a DatasourceName element */ public boolean requiresDatasourceName() { return this != DISCOVER_DATASOURCES; } /** * Returns whether this request requires a {@code <CatalogName>} * element. * * @return whether this request requires a CatalogName element */ public boolean requiresCatalogName() { // If we don't specifiy CatalogName in the properties of an // MDSCHEMA_FUNCTIONS request, Mondrian's XMLA provider will give // us the whole set of functions multiplied by the number of // catalogs. JDBC (and Mondrian) assumes that functions belong to a // catalog whereas XMLA (and SSAS) assume that functions belong to // the database. Always specifying a catalog is the easiest way to // reconcile them. return this == MDSCHEMA_FUNCTIONS; } /** * Returns whether this request allows a {@code <CatalogName>} * element in the properties section of the request. Even for requests * that allow it, it is usually optional. * * @return whether this request allows a CatalogName element */ public boolean allowsCatalogName() { return true; } /** * Returns the column with a given name, or null if there is no such * column. * * @param name * Column name * @return Column, or null if not found */ public MetadataColumn getColumn(String name) { return columnsByName.get(name); } /** * Same as getColumn() but returns index. * * @param name * @return */ public int getColumnIndex(String name) { return indexByName.get(name); } public boolean allowsLocale() { return name().startsWith("MDSCHEMA"); } } private static final Pattern LOWERCASE_PATTERN = Pattern .compile(".*[a-z].*"); static class MetadataColumn { final String name; final String xmlaName; MetadataColumn(String xmlaName, String name) { this.xmlaName = xmlaName; this.name = name; } MetadataColumn(String xmlaName) { this.xmlaName = xmlaName; String name = xmlaName; if (LOWERCASE_PATTERN.matcher(name).matches()) { name = Olap4jUtil.camelToUpper(name); } // VALUE is a SQL reserved word if (name.equals("VALUE")) { name = "PROPERTY_VALUE"; } this.name = name; } } private static class XmlaOlap4jMdxValidator implements MdxValidator { private final OlapConnection connection; XmlaOlap4jMdxValidator(OlapConnection connection) { this.connection = connection; } public SelectNode validateSelect(SelectNode selectNode) throws OlapException { StringWriter sw = new StringWriter(); selectNode.unparse(new ParseTreeWriter(sw)); String mdx = sw.toString(); final Olap4ldConnection olap4jConnection = (Olap4ldConnection) connection; return selectNode; } } } // End XmlaOlap4jConnection.java