com.jaspersoft.jasperserver.war.xmla.XmlaRepositoryImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.jaspersoft.jasperserver.war.xmla.XmlaRepositoryImpl.java

Source

/*
 * Copyright (C) 2005 - 2014 TIBCO Software Inc. All rights reserved.
 * http://www.jaspersoft.com.
 *
 * Unless you have purchased  a commercial license agreement from Jaspersoft,
 * the following license terms  apply:
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License  as
 * published by the Free Software Foundation, either version 3 of  the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero  General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public  License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package com.jaspersoft.jasperserver.war.xmla;

import com.jaspersoft.jasperserver.api.metadata.olap.domain.MondrianXMLADefinition;
import com.jaspersoft.jasperserver.api.metadata.olap.service.UpdatableXMLAContainer;
import com.jaspersoft.jasperserver.api.metadata.user.domain.impl.client.MetadataUserDetails;
import mondrian.olap.DriverManager;
import mondrian.olap.MondrianServer;
import mondrian.olap.Util;
import mondrian.olap4j.MondrianOlap4jDriver;
import mondrian.rolap.RolapConnection;
import mondrian.rolap.RolapConnectionProperties;
import mondrian.rolap.RolapSchema;
import mondrian.server.JsMondrianServerRegistry;
import mondrian.server.Repository;
import mondrian.spi.CatalogLocator;
import mondrian.util.ClassResolver;
import mondrian.util.LockBox;
import mondrian.xmla.DataSourcesConfig;
import mondrian.xmla.XmlaHandler;
import org.olap4j.OlapConnection;
import org.olap4j.OlapException;
import org.olap4j.impl.Olap4jUtil;
import org.springframework.security.Authentication;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.web.context.request.RequestContextHolder;

import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.apache.commons.lang.StringUtils.isNotEmpty;

/**
 * Implementation of {@link mondrian.server.Repository} that reads from JRS databases.
 *
 * @author vsabadosh
 * @version $Id: XmlaRepositoryImpl.java 47331 2014-07-18 09:13:06Z kklein $
 */
public class XmlaRepositoryImpl implements Repository, UpdatableXMLAContainer {

    protected XmlaContentFinder xmlaContentFinder;
    private CatalogLocator locator;
    private long cacheTimeout;
    private String catalogDelimiter;

    private final Map<String, CacheElement<Properties>> olapConnectionPropertiesCache = new ConcurrentHashMap<String, CacheElement<Properties>>();

    private final Map<String, CacheElement<ServerInfo>> serverInfoCache = new ConcurrentHashMap<String, CacheElement<ServerInfo>>();

    public void setXmlaContentFinder(XmlaContentFinder xmlaContentFinder) {
        this.xmlaContentFinder = xmlaContentFinder;
    }

    public void setLocator(CatalogLocator locator) {
        this.locator = locator;
    }

    public void setCacheTimeout(long cacheTimeout) {
        this.cacheTimeout = cacheTimeout;
    }

    public void setCatalogDelimiter(String catalogDelimiter) {
        this.catalogDelimiter = catalogDelimiter;
    }

    @Override
    public List<String> getDatabaseNames(RolapConnection connection) {
        return new ArrayList<String>(getServerInfo().datasourceMap.keySet());
    }

    @Override
    public List<String> getCatalogNames(RolapConnection connection, String databaseName) {
        return new ArrayList<String>(getServerInfo().getDatabaseInfo(databaseName).getCatalogAsMap().keySet());
    }

    @Override
    public Map<String, RolapSchema> getRolapSchemas(RolapConnection connection, String databaseName,
            String catalogName) {
        final RolapSchema schema = connection.getSchema();
        return Collections.singletonMap(schema.getName(), schema);
    }

    @Override
    public List<Map<String, Object>> getDatabases(RolapConnection connection) {
        final List<Map<String, Object>> propsList = new ArrayList<Map<String, Object>>();
        for (DatabaseInfo dsInfo : getServerInfo().datasourceMap.values()) {
            propsList.add(dsInfo.properties);
        }
        return propsList;
    }

    @Override
    public void clearCache() {
        synchronized (this.serverInfoCache) {
            serverInfoCache.clear();
        }
    }

    @Override
    public void shutdown() {
        // nothing to do
    }

    @Override
    public void updateXMLAConnection(MondrianXMLADefinition oldDef, MondrianXMLADefinition newDef) {
        clearCache();
    }

    @Override
    public OlapConnection getConnection(MondrianServer server, String databaseName, String catalogName,
            String roleName, Properties props) throws SQLException {
        final ServerInfo serverInfo = getServerInfo();
        final DatabaseInfo databaseInfo;

        if (databaseName == null) {
            if (serverInfo.datasourceMap.size() == 0) {
                throw new OlapException("No databases configured on this server");
            }
            databaseInfo = serverInfo.datasourceMap.values().iterator().next();
        } else {
            databaseInfo = serverInfo.datasourceMap.get(databaseName);
        }

        if (databaseInfo == null) {
            throw Util.newError("Unknown database '" + databaseName + "'");
        }

        CatalogInfo catalogInfo = null;
        Map<String, CatalogInfo> catalogsMap = databaseInfo.getCatalogAsMap();

        Iterator<CatalogInfo> catalogInfoIterator = catalogsMap.values().iterator();

        if (isEmpty(catalogName)) {
            if (catalogsMap.size() == 0) {
                throw new OlapException("No catalogs in the database named " + databaseInfo.name);
            }
        } else {
            catalogInfo = databaseInfo.getCatalogByName(catalogName, catalogsMap);
            if (catalogInfo == null) {
                throw Util.newError("Unknown catalog '" + catalogName + "'");
            }
        }
        //Loop because in the case of catalogName == null we need find the default valid catalog.
        do {
            try {
                if (isEmpty(catalogName)) {
                    catalogInfo = catalogInfoIterator.next();
                }

                // Now create the connection
                return getOlapConnection(
                        getMondrianConnectionProperties(server, databaseInfo, catalogInfo, roleName, props));
            } catch (Exception ex) {
                if ((isNotEmpty(catalogName)) || !catalogInfoIterator.hasNext()) {
                    throw new RuntimeException(ex.getMessage(), ex);
                }
            }
        } while (isEmpty(catalogName) && catalogInfoIterator.hasNext());

        // Is not reachable.
        throw new OlapException("All XML/A catalogs are not valid.");
    }

    protected OlapConnection getOlapConnection(Properties properties) throws SQLException {
        final java.sql.Connection connection = java.sql.DriverManager.getConnection("jdbc:mondrian:", properties);
        return connection.unwrap(OlapConnection.class);
    }

    private Properties getMondrianConnectionProperties(MondrianServer server, DatabaseInfo databaseInfo,
            CatalogInfo catalogInfo, String roleName, Properties props) throws SQLException {
        //Get current session id
        String currentSessionId = getCurrentUserSessionId();
        //Generated cached key based on authorized session id, datasource, catalog name and tenant id.
        String cacheKey = currentSessionId + "_" + databaseInfo.name + "_" + catalogInfo.name + "_"
                + catalogInfo.tenantId;

        Map<String, Object> connectProperties = new HashMap<String, Object>();
        connectProperties.putAll(databaseInfo.properties);
        connectProperties.put("DataSourceInfo", catalogInfo.connectString);

        synchronized (this.olapConnectionPropertiesCache) {
            cleanExpired(this.olapConnectionPropertiesCache);

            Properties properties;
            if (olapConnectionPropertiesCache.get(cacheKey) != null) {
                properties = olapConnectionPropertiesCache.get(cacheKey).getValue();
            } else {
                properties = xmlaContentFinder.getMondrianConnectionProperties(connectProperties, roleName);
                olapConnectionPropertiesCache.put(cacheKey, new CacheElement<Properties>(properties));
            }

            // Save the server for the duration of the call to 'getConnection'.
            final LockBox.Entry entry = JsMondrianServerRegistry.INSTANCE.getLockBox().register(server);
            properties.setProperty(RolapConnectionProperties.Instance.name(), entry.getMoniker());

            // Make sure we load the Mondrian driver into the ClassLoader.
            try {
                ClassResolver.INSTANCE.forName(MondrianOlap4jDriver.class.getName(), true);
            } catch (ClassNotFoundException e) {
                throw new OlapException("Cannot find mondrian olap4j driver.");
            }

            if (props != null && props.containsKey(XmlaHandler.JDBC_LOCALE)) {
                properties.put(XmlaHandler.JDBC_LOCALE, props.get(XmlaHandler.JDBC_LOCALE));
            }

            return properties;
        }
    }

    private ServerInfo getServerInfo() {
        synchronized (this.serverInfoCache) {
            String currentSessionId = getCurrentUserSessionId();

            cleanExpired(serverInfoCache);
            if (serverInfoCache.get(currentSessionId) == null) {
                DataSourcesConfig.DataSources xmlDataSources = xmlaContentFinder.getDataSources();
                ServerInfo serverInfo = new ServerInfo();

                if (xmlDataSources != null && xmlDataSources.dataSources != null) {
                    for (DataSourcesConfig.DataSource xmlDataSource : xmlDataSources.dataSources) {
                        final Map<String, Object> dsPropsMap = Olap4jUtil.<String, Object>mapOf("DataSourceName",
                                xmlDataSource.getDataSourceName(), "DataSourceDescription",
                                xmlDataSource.getDataSourceDescription(), "URL", xmlDataSource.getURL(),
                                "DataSourceInfo", xmlDataSource.getDataSourceName(), "ProviderName",
                                xmlDataSource.getProviderName(), "ProviderType", xmlDataSource.providerType,
                                "AuthenticationMode", xmlDataSource.authenticationMode);
                        final DatabaseInfo databaseInfo = new DatabaseInfo(xmlDataSource.name, dsPropsMap);
                        serverInfo.datasourceMap.put(xmlDataSource.name, databaseInfo);
                        if (xmlDataSource.catalogs != null && xmlDataSource.catalogs.catalogs != null) {
                            for (DataSourcesConfig.Catalog xmlCatalog : xmlDataSource.catalogs.catalogs) {
                                final CatalogInfo catalogInfo = new CatalogInfo(xmlCatalog,
                                        xmlDataSource.dataSourceInfo, locator);
                                if (databaseInfo.catalogsByTenantIdMap.get(catalogInfo.tenantId) != null) {
                                    databaseInfo.catalogsByTenantIdMap.get(catalogInfo.tenantId).add(catalogInfo);
                                } else {
                                    List<CatalogInfo> catalogInfos = new ArrayList<CatalogInfo>();
                                    catalogInfos.add(catalogInfo);
                                    databaseInfo.catalogsByTenantIdMap.put(catalogInfo.tenantId, catalogInfos);
                                }
                            }
                        }
                    }
                }
                serverInfoCache.put(currentSessionId, new CacheElement<ServerInfo>(serverInfo));

                return serverInfo;
            } else {
                return serverInfoCache.get(currentSessionId).getValue();
            }
        }
    }

    private class ServerInfo {
        private Map<String, DatabaseInfo> datasourceMap = new HashMap<String, DatabaseInfo>();

        private DatabaseInfo getDatabaseInfo(String databaseName) {
            DatabaseInfo databaseInfo = datasourceMap.get(databaseName);
            if (databaseInfo != null) {
                return databaseInfo;
            } else {
                throw Util.newError("Unknown database '" + databaseName + "'");
            }
        }
    }

    private class DatabaseInfo {
        private final String name;
        private final Map<String, Object> properties;
        private Map<String, List<CatalogInfo>> catalogsByTenantIdMap = new HashMap<String, List<CatalogInfo>>();

        DatabaseInfo(String name, Map<String, Object> properties) {
            this.name = name;
            this.properties = properties;
        }

        Map<String, CatalogInfo> getCatalogAsMap() {
            Map<String, CatalogInfo> catalogsMap = new HashMap<String, CatalogInfo>();

            for (String tenantId : catalogsByTenantIdMap.keySet()) {
                catalogsMap.putAll(getCatalogsMapForId(tenantId));
            }
            return catalogsMap;
        }

        private Map<String, CatalogInfo> getCatalogsMapForId(String tenantId) {
            Map<String, CatalogInfo> catalogsMap = new HashMap<String, CatalogInfo>();

            if (catalogsByTenantIdMap.get(tenantId) == null) {
                return catalogsMap;
            }

            for (CatalogInfo catalogInfo : catalogsByTenantIdMap.get(tenantId)) {
                String catalogKey;

                if (isNotEmpty(tenantId) && !tenantId.equals(getCurrentUserTenantId())) {
                    catalogKey = catalogInfo.name + catalogDelimiter + tenantId;
                } else {
                    catalogKey = catalogInfo.name;
                }

                if (catalogsMap.containsKey(catalogKey)) {
                    CatalogInfo catalogInfoDuplicate = catalogsMap.get(catalogKey);
                    if (catalogInfo.tenantId.equals(catalogInfoDuplicate.tenantId)) {
                        throw Util.newError("More than one Catalog object has name '" + catalogKey + "'");
                    } else if (isNotEmpty(catalogInfo.tenantId)) {
                        catalogsMap.put(catalogKey, catalogInfo);
                    }
                }
                catalogsMap.put(catalogKey, catalogInfo);
            }

            return catalogsMap;
        }

        CatalogInfo getCatalogByName(String name, Map<String, CatalogInfo> catalogsMap) {
            CatalogInfo catalogInfo = null;
            if (catalogsMap.containsKey(name)) {
                catalogInfo = catalogsMap.get(name);
            } else {
                for (String key : catalogsMap.keySet()) {
                    //Searching over another organization catalogs.
                    if (key.startsWith(name + catalogDelimiter)) {
                        catalogInfo = catalogsMap.get(key);
                        break;
                    }
                }
            }
            return catalogInfo;
        }

    }

    private class CatalogInfo {
        private final String connectString;
        private RolapSchema rolapSchema; // populated on demand
        private final String olap4jConnectString;
        private final CatalogLocator locator;
        private final String tenantId;
        private final String name;

        CatalogInfo(DataSourcesConfig.Catalog catalog, String dataSourceInfo, CatalogLocator locator) {
            this.name = catalog.name;
            this.locator = locator;
            this.tenantId = catalog.definition;
            this.connectString = catalog.dataSourceInfo != null ? catalog.dataSourceInfo : dataSourceInfo;

            this.olap4jConnectString = connectString.startsWith("jdbc:") ? connectString
                    : "jdbc:mondrian:" + connectString;
        }

        private RolapSchema getRolapSchema() {
            if (rolapSchema == null) {
                RolapConnection rolapConnection = null;
                try {
                    rolapConnection = (RolapConnection) DriverManager.getConnection(connectString, this.locator);
                    rolapSchema = rolapConnection.getSchema();
                } finally {
                    if (rolapConnection != null) {
                        rolapConnection.close();
                    }
                }
            }
            return rolapSchema;
        }
    }

    protected String getCurrentUserTenantId() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null) {
            MetadataUserDetails userDetails = (MetadataUserDetails) auth.getPrincipal();
            if (userDetails != null) {
                return userDetails.getTenantId();
            }
        }
        return null;
    }

    protected String getCurrentUserSessionId() {
        return RequestContextHolder.currentRequestAttributes().getSessionId();
    }

    private class CacheElement<T> {
        private T value;
        private AtomicLong timestamp = new AtomicLong(Calendar.getInstance().getTimeInMillis());

        public CacheElement(T value) {
            this.value = value;
        }

        private T getValue() {
            return value;
        }

        private AtomicLong getTimestamp() {
            return timestamp;
        }
    }

    private <T> void cleanExpired(Map<String, CacheElement<T>> cacheMap) {
        for (Map.Entry<String, CacheElement<T>> entry : cacheMap.entrySet()) {
            // Check if not expired
            if (Calendar.getInstance()
                    .getTimeInMillis() > (entry.getValue().getTimestamp().longValue() + (cacheTimeout * 1000))) {
                // Evicts it.
                cacheMap.remove(entry.getKey());
            }
        }
    }

}