com.jaspersoft.jasperserver.api.metadata.olap.service.impl.OlapConnectionServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.jaspersoft.jasperserver.api.metadata.olap.service.impl.OlapConnectionServiceImpl.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.api.metadata.olap.service.impl;

import com.jaspersoft.jasperserver.api.JSException;
import com.jaspersoft.jasperserver.api.common.domain.ExecutionContext;
import com.jaspersoft.jasperserver.api.common.domain.ValidationResult;
import com.jaspersoft.jasperserver.api.common.domain.impl.ExecutionContextImpl;
import com.jaspersoft.jasperserver.api.common.domain.impl.ValidationDetailImpl;
import com.jaspersoft.jasperserver.api.common.domain.impl.ValidationResultImpl;
import com.jaspersoft.jasperserver.api.common.service.JdbcDriverService;
import com.jaspersoft.jasperserver.api.common.util.StaticCharacterEncodingProvider;
import com.jaspersoft.jasperserver.api.metadata.common.domain.FileResource;
import com.jaspersoft.jasperserver.api.metadata.common.domain.FileResourceData;
import com.jaspersoft.jasperserver.api.metadata.common.domain.Folder;
import com.jaspersoft.jasperserver.api.metadata.common.domain.Resource;
import com.jaspersoft.jasperserver.api.metadata.common.domain.ResourceReference;
import com.jaspersoft.jasperserver.api.metadata.common.domain.client.FolderImpl;
import com.jaspersoft.jasperserver.api.metadata.common.service.RepositoryService;
import com.jaspersoft.jasperserver.api.metadata.common.util.JndiFallbackResolver;
import com.jaspersoft.jasperserver.api.metadata.jasperreports.domain.JdbcReportDataSource;
import com.jaspersoft.jasperserver.api.metadata.jasperreports.domain.JndiJdbcReportDataSource;
import com.jaspersoft.jasperserver.api.metadata.jasperreports.domain.ReportDataSource;
import com.jaspersoft.jasperserver.api.metadata.jasperreports.service.ReportDataSourceService;
import com.jaspersoft.jasperserver.api.metadata.jasperreports.service.ReportDataSourceServiceFactory;
import com.jaspersoft.jasperserver.api.metadata.olap.domain.MondrianConnection;
import com.jaspersoft.jasperserver.api.metadata.olap.domain.OlapClientConnection;
import com.jaspersoft.jasperserver.api.metadata.olap.domain.OlapUnit;
import com.jaspersoft.jasperserver.api.metadata.olap.domain.XMLAConnection;
import com.jaspersoft.jasperserver.api.metadata.olap.service.OlapConnectionService;
import com.jaspersoft.jasperserver.api.metadata.olap.service.XMLATestResult;
import com.jaspersoft.jasperserver.api.metadata.olap.service.XMLATestResult.XMLATestCode;
import com.jaspersoft.jasperserver.api.metadata.user.domain.User;
import com.jaspersoft.jasperserver.api.metadata.user.domain.impl.client.MetadataUserDetails;
import com.jaspersoft.jasperserver.api.metadata.user.service.TenantService;
import com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService;
import com.tonbeller.jpivot.core.Model;
import com.tonbeller.jpivot.core.ModelFactory;
import com.tonbeller.jpivot.mondrian.MondrianModel;
import com.tonbeller.jpivot.olap.model.OlapModel;
import com.tonbeller.jpivot.tags.MondrianOlapModelTag;
import com.tonbeller.jpivot.tags.OlapModelProxy;
import com.tonbeller.jpivot.xmla.XMLA_Model;
import com.tonbeller.jpivot.xmla.XMLA_OlapModelTag;
import com.tonbeller.wcf.controller.RequestContext;
import mondrian.olap.Connection;
import mondrian.olap.DriverManager;
import mondrian.olap.MondrianException;
import mondrian.olap.Util;
import mondrian.rolap.RolapConnectionProperties;
import mondrian.spi.CatalogLocator;
import mondrian.util.Pair;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.olap4j.OlapConnection;
import org.olap4j.OlapException;
import org.olap4j.OlapWrapper;
import org.olap4j.driver.xmla.XmlaOlap4jDriver;
import org.olap4j.metadata.Catalog;
import org.olap4j.metadata.NamedList;
import org.olap4j.metadata.Schema;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.security.Authentication;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.NoInitialContextException;
import javax.servlet.http.HttpSession;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.net.ConnectException;
import java.net.SocketException;
import java.net.URL;
import java.net.UnknownHostException;
import java.sql.ResultSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;

/**
 * @author sbirney
 * $Id: OlapConnectionServiceImpl.java 47331 2014-07-18 09:13:06Z kklein $
 */
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class OlapConnectionServiceImpl implements OlapConnectionService, ReportDataSourceServiceFactory {

    /**
     * property keys used for olap4j connection
     */
    public static final String OLAP4J_DRIVER = "olap4jDriver";
    public static final String OLAP4J_URL_PREFIX = "urlPrefix";
    public static final String XMLA_USER = "user";
    public static final String XMLA_PASSWORD = "password";

    private static final String OLAP_CONNECTION_JNDI_DATA_SOURCE = "DataSource";
    private static final String OLAP_CONNECTION_JDBC = "Jdbc";
    private static final String OLAP_CONNECTION_JDBC_USER = "JdbcUser";
    private static final String OLAP_CONNECTION_JDBC_PASSWORD = "JdbcPassword";

    private static final String DATA_SOURCE_INFO = "DataSourceInfo";

    private static final Log log = LogFactory.getLog(OlapConnectionServiceImpl.class);

    private UserAuthorityService userService;
    private TenantService tenantService;
    private JndiFallbackResolver jndiFallbackResolver;
    private JdbcDriverService jdbcDriverService;

    private CatalogLocator repositoryCatalogLocator = new RepositoryCatalogLocator();

    protected String OLAP4J_CACHE = null;
    protected String OLAP4J_CACHE_NAME = null;
    protected String OLAP4J_CACHE_MODE = null;
    protected String OLAP4J_CACHE_TIMEOUT = null;
    protected String OLAP4J_CACHE_SIZE = null;

    /*
     * (non-Javadoc)
     *
     * @see com.jaspersoft.jasperserver.api.metadata.olap.service.OlapConnectionService.createOlapModel()
     *
     * @return a newly constructed and configured OlapModel initialize is not
     * yet called.
     */
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public OlapModel createOlapModel(ExecutionContext context, OlapUnit olapUnit) {
        OlapClientConnection clientConn = (OlapClientConnection) dereference(context,
                olapUnit.getOlapClientConnection());
        if (clientConn instanceof XMLAConnection) {
            return createXmlaModel(context, olapUnit);
        }
        String mdx = olapUnit.getMdxQuery();
        MondrianConnection conn = (MondrianConnection) clientConn;
        /*
         * FIXME Need to be able to configure the extensions
         *
         * URL url; if (config == null) url = getDefaultConfig(); else url =
         * pageContext.getServletContext().getResource(config);
         */
        MondrianModel model = null;
        try {
            model = (MondrianModel) ModelFactory.instance(getDefaultMondrianConfig());
        } catch (Exception e) {
            throw new JSException(e);
        }

        model.setMdxQuery(mdx);
        model.setConnectProperties(getMondrianConnectProperties(context, conn));
        model.setDynLocale(String.valueOf(LocaleContextHolder.getLocale()));

        // Set the catalog locator
        model.setCatalogLocator(repositoryCatalogLocator);

        /*
         * FIXME use of other values?
         *
         * mm.setDynresolver(cfg.getDynResolver());
         * mm.setDynLocale(cfg.getDynLocale()); if
         * ("false".equalsIgnoreCase(cfg.getConnectionPooling()))
         * mm.setConnectionPooling(false);
         * mm.setExternalDataSource(cfg.getExternalDataSource());
         */
        return model;
    }

    protected URL getDefaultMondrianConfig() {
        return MondrianOlapModelTag.class.getResource("/com/tonbeller/jpivot/mondrian/config.xml");
    }

    protected URL getDefaultXMLAConfig() {
        return XMLA_OlapModelTag.class.getResource("config.xml");
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public OlapModel createXmlaModel(ExecutionContext context, OlapUnit xmlaUnit) {
        String mdx = xmlaUnit.getMdxQuery();
        XMLAConnection xmlaConn = (XMLAConnection) dereference(context, xmlaUnit.getOlapClientConnection());

        URL url;
        /*
         * if (config == null) url = getClass().getResource("config.xml"); else
         * url = pageContext.getServletContext().getResource(config);
         */
        url = getDefaultXMLAConfig();

        // let Digester create a model from config input
        // the config input stream MUST refer to the XMLA_Model class
        // <model class="com.tonbeller.bii.xmla.XMLA_Model"> is required
        Model model;
        try {
            model = ModelFactory.instance(url);
        } catch (Exception e) {
            throw new JSException(e);
        }

        if (!(model instanceof XMLA_Model))
            throw new JSException("jsexception.invalid.class.attribute.for.model.tag",
                    new Object[] { getDefaultXMLAConfig() });

        XMLA_Model xmlaModel = (XMLA_Model) model;

        xmlaModel.setCatalog(xmlaConn.getCatalog());

        // if the xmlaConnection metadata object does not specify
        // username or password, then use the login-in user's credentials
        if (lacksAuthentication(xmlaConn)) {
            User user = getCurrentUserDetails();
            String fullyQualifiedName = user.getUsername();
            if (user.getTenantId() != null) {
                fullyQualifiedName += tenantService.getUserOrgIdDelimiter() + user.getTenantId();
            }
            xmlaModel.setUser(fullyQualifiedName);
            xmlaModel.setPassword(user.getPassword());
        } else {
            xmlaModel.setUser(xmlaConn.getUsername());
            xmlaModel.setPassword(xmlaConn.getPassword());
        }

        xmlaModel.setDataSource(xmlaConn.getDataSource());

        xmlaModel.setMdxQuery(mdx);
        xmlaModel.setID(xmlaConn.getCatalog() + "-" + xmlaUnit.hashCode()); // ???
        xmlaModel.setUri(xmlaConn.getURI());

        log.debug("XMLA USERNAME = " + xmlaModel.getUser());
        log.debug("XMLA PASSWORD = " + xmlaModel.getPassword());

        return xmlaModel;
    }

    protected boolean lacksAuthentication(XMLAConnection xmlaConn) {
        return xmlaConn.getUsername() == null || xmlaConn.getPassword() == null || xmlaConn.getUsername().equals("")
                || xmlaConn.getPassword().equals("");
    }

    protected MetadataUserDetails getCurrentUserDetails() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null) {
            return (MetadataUserDetails) auth.getPrincipal();
        }
        return null;
    }

    /*
     * Because of the way the repository works, this version of validate
     * only works if the OlapUnit has already been saved.
     */
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public ValidationResult validate(ExecutionContext context, OlapUnit unit) {
        return validate(context, unit, null, null, null);
    }

    /*
     * if your OlapUnit has not yet been saved as a repository resource,
     * you must call this version of validate and pass in the schema, connection,
     * and datasource.
     */

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public ValidationResult validate(ExecutionContext context, OlapUnit unit, FileResource schema,
            OlapClientConnection conn, ReportDataSource dataSource) {
        ValidationResultImpl result = new ValidationResultImpl();
        validateMDX(context, result, unit, schema, conn, dataSource);
        // TODO: validate the datasource as well
        return result;
    }

    /*
     * Because of the way the repository works, this version of validateMDX
     * only works if the OlapUnit has already been saved.
     */
    protected void validateMDX(ExecutionContext context, ValidationResultImpl result, OlapUnit unit) {
        validateMDX(context, result, unit, null, null, null);
    }

    protected void validateMDX(ExecutionContext context, ValidationResultImpl result, OlapUnit unit,
            FileResource schema, OlapClientConnection conn, ReportDataSource dataSource) {
        MondrianConnection resource = null;
        if (conn instanceof MondrianConnection)
            resource = (MondrianConnection) conn;

        if (resource == null)
            resource = getConnectionResource(context, unit);

        if (resource == null)
            return;

        try {
            mondrian.olap.Connection monConnection = getTestMondrianConnection(context, resource, dataSource,
                    schema);
            monConnection.parseQuery(unit.getMdxQuery());
        } catch (Exception e) {
            ValidationDetailImpl detail = new ValidationDetailImpl();
            detail.setValidationClass(OlapUnit.class);
            detail.setName(unit.getName());
            detail.setLabel(unit.getLabel());
            detail.setResult(ValidationResult.STATE_ERROR);
            detail.setException(e);
            detail.setMessage(e.getMessage());
            result.addValidationDetail(detail);
            log.warn("Validation Failed for Olap Unit: " + unit.getName(), e);
        }

    }

    /*
     * mondrianConnection
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public MondrianConnection getConnectionResource(ExecutionContext context, OlapUnit unit) {
        Resource clientConn = dereference(context, unit.getOlapClientConnection());
        if (clientConn instanceof MondrianConnection) {
            return (MondrianConnection) clientConn;
        }
        if (clientConn instanceof XMLAConnection) {
            // TODO: have to find the matching MondrianXMLADefinition
        }
        return null;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public mondrian.olap.Connection getMondrianConnection(ExecutionContext context, String connResourceName) {
        MondrianConnection conn = (MondrianConnection) getRepository().getResource(context, connResourceName);

        if (conn == null) {
            log.error("missing MondrianConnection resource: " + connResourceName);
            throw new JSException("jsexception.mondrian.no.connection.for.resource",
                    new Object[] { connResourceName });
        }
        return getMondrianConnection(context, conn);
    }

    private mondrian.olap.Connection getMondrianConnection(ExecutionContext context, MondrianConnection conn) {
        return getMondrianConnection(context, conn, null);
    }

    protected mondrian.olap.Connection getMondrianConnection(ExecutionContext context, MondrianConnection conn,
            ReportDataSource dataSource) {
        Util.PropertyList connectProps = getMondrianConnectProperties(context, conn, dataSource);

        try {
            return DriverManager.getConnection(connectProps, repositoryCatalogLocator);
        } catch (MondrianException e) {
            String dataSourceString = connectProps.get(OLAP_CONNECTION_JNDI_DATA_SOURCE);

            if (ExceptionUtils.indexOfThrowable(e, NoInitialContextException.class) > 0
                    && dataSourceString != null) {
                Map<String, String> jdbcProperties = jndiFallbackResolver.getJdbcPropertiesMap(dataSourceString);

                try {
                    // Remove JNDI reference.
                    connectProps.remove(OLAP_CONNECTION_JNDI_DATA_SOURCE);

                    // Add JDBC url, username and password.
                    connectProps.put(OLAP_CONNECTION_JDBC, jdbcProperties.get(JndiFallbackResolver.JDBC_URL));
                    connectProps.put(OLAP_CONNECTION_JDBC_USER,
                            jdbcProperties.get(JndiFallbackResolver.JDBC_USERNAME));
                    connectProps.put(OLAP_CONNECTION_JDBC_PASSWORD,
                            jdbcProperties.get(JndiFallbackResolver.JDBC_PASSWORD));

                    return DriverManager.getConnection(connectProps, repositoryCatalogLocator);
                } catch (Throwable t) {
                    throw new JSException("Error getting connection from jndi fallback properties.", t);
                }
            } else {
                throw e;
            }
        }
    }

    protected mondrian.olap.Connection getTestMondrianConnection(ExecutionContext context, MondrianConnection conn,
            ReportDataSource dataSource, FileResource mondrianSchema) throws Exception {
        Util.PropertyList connectProps = getMondrianConnectProperties(context, conn, dataSource);
        if (mondrianSchema != null && mondrianSchema.getData() != null) {
            //Remove Catalog Uri since Catalogcontent
            connectProps.remove(RolapConnectionProperties.Catalog.toString());
            connectProps.put(RolapConnectionProperties.CatalogContent.toString(),
                    new String(mondrianSchema.getData(), "UTF-8"));
        }

        return DriverManager.getConnection(connectProps, null);
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public void initializeAndShow(OlapModelProxy omp, String viewUri, OlapModel model, OlapUnit unit)
            throws Exception {
        omp.initializeAndShow(viewUri, model);
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public OlapModel initializeOlapModel(ExecutionContext executionContext, OlapUnit olapUnit, HttpSession sess) {

        RequestContext context = RequestContext.instance();

        OlapModel model = createOlapModel(executionContext, olapUnit);

        if (model == null) {
            throw new JSException("jsexception.no.olap.model.created.for",
                    new Object[] { olapUnit.getURIString() });
        }

        model = (OlapModel) model.getTopDecorator();
        model.setLocale(context.getLocale());
        model.setServletContext(sess.getServletContext());
        model.setID(olapUnit.getURIString());

        /*
         ClickableExtension ext = (ClickableExtension) model.getExtension(ClickableExtension.ID);
         if (ext == null) {
         ext = new ClickableExtensionImpl();
         model.addExtension(ext);
         }
         ext.setClickables(clickables);
         */
        // stackMode
        OlapModelProxy omp = OlapModelProxy.instance(olapUnit.getURIString(), sess, false);
        /*       if (queryName != null)
         omp.initializeAndShow(queryName, model);
         else
         */
        try {
            initializeAndShow(omp, olapUnit.getURIString(), model, olapUnit);
        } catch (Exception e) {
            throw new JSException(e);
        }

        return omp;
    }

    // this could be a general repository method too...
    @Transactional(propagation = Propagation.REQUIRED)
    public String getFileResourceData(ExecutionContext context, FileResource file) {
        RepositoryService rep = getRepository();
        StringBuffer fileString = new StringBuffer();
        InputStream data;
        if (file.hasData()) {
            data = file.getDataStream();
        } else {
            log.debug("FILE URI STRING = " + file.getURIString());
            FileResourceData resourceData = rep.getResourceData(context, file.getURIString());
            data = resourceData.getDataStream();
        }
        log.debug("FILE = " + file);

        // use character encoding
        String encoding = getEncodingProvider().getCharacterEncoding();
        String line;
        try {
            BufferedReader in = new BufferedReader(new InputStreamReader(data, encoding));
            while ((line = in.readLine()) != null) {
                fileString.append(line);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return fileString.toString();
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public Util.PropertyList getOlapConnectProperties(ExecutionContext context, OlapClientConnection conn) {
        if (conn instanceof MondrianConnection) {
            log.debug("go fetch Mondrian Connection Properties");
            return getMondrianConnectProperties(context, (MondrianConnection) conn, null);

        } else if (conn instanceof XMLAConnection) {
            log.debug("go fetch XMLConnection Properties");
            return getXMLAConnectProperties(context, (XMLAConnection) conn);

        } else {
            throw new IllegalArgumentException("unknown repo connection type " + conn.getClass().getName());
        }
    }

    /**
     * from an xml/a connection, get a prop list suitable for olap4j
     * @param context
     * @param conn
     */
    private Util.PropertyList getXMLAConnectProperties(ExecutionContext context, XMLAConnection conn) {
        Util.PropertyList connectProps = new Util.PropertyList();
        connectProps.put(XmlaOlap4jDriver.Property.SERVER.toString(), conn.getURI());
        connectProps.put(XmlaOlap4jDriver.Property.CATALOG.toString(), conn.getCatalog());

        /////////////////////////////////////////////////////
        //
        // http://bugzilla.jaspersoft.com/show_bug.cgi?id=22563
        // 2011-04-19  thorick   Be sure to append any TenantID here
        //                       else the returned DataSource names won't match
        //                       what the XMLA Olap4j Connection expects to see.
        //
        String dataSource = conn.getDataSource();
        // This code breaks XMLA connectivity to non JRS data sources, so it has to go
        //        MetadataUserDetails userDetails = getCurrentUserDetails();
        //        String username = null;
        //        String tenantId = null;
        //        if (userDetails != null) {
        //          username = userDetails.getUsername();
        //          tenantId = userDetails.getTenantId();
        //
        //          dataSource = dataSource + "TenantID=" + tenantId + ";";
        //          log.debug("Appending TenantId to DataSource name '"+dataSource+"'");
        //        }

        connectProps.put(XmlaOlap4jDriver.Property.DATABASE.toString(), dataSource);
        connectProps.put(XMLA_USER, conn.getUsername());
        connectProps.put(XMLA_PASSWORD, conn.getPassword());
        // what olap4j driver & prefix?
        connectProps.put(OLAP4J_DRIVER, "org.olap4j.driver.xmla.XmlaOlap4jDriver");
        connectProps.put(OLAP4J_URL_PREFIX, "jdbc:xmla:");
        return connectProps;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public Util.PropertyList getMondrianConnectProperties(ExecutionContext context, MondrianConnection conn) {
        return getMondrianConnectProperties(context, conn, null);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public Util.PropertyList getMondrianConnectProperties(ExecutionContext context, MondrianConnection conn,
            ReportDataSource dataSource) {

        // assemble a mondrian connection PropertyList
        if (dataSource == null) {
            dataSource = (ReportDataSource) dereference(context, conn.getDataSource());
            if (dataSource == null) {
                throw new JSException(
                        "null data source on dereference of mondrian connection " + conn.getURIString() + " for "
                                + (conn.getDataSource().isLocal()
                                        ? "local: " + conn.getDataSource().getLocalResource().getURIString()
                                        : conn.getDataSource().getReferenceURI()));
            }
        }

        Util.PropertyList connectProps = new Util.PropertyList();

        connectProps.put(RolapConnectionProperties.Provider.toString(), "mondrian");
        connectProps.put(OLAP4J_DRIVER, "mondrian.olap4j.MondrianOlap4jDriver");
        connectProps.put(OLAP4J_URL_PREFIX, "jdbc:mondrian:");
        connectProps.put(RolapConnectionProperties.Locale.toString(), LocaleContextHolder.getLocale().toString());

        /*
         * The URI here has to be an "internal" representation (in multi-tenancy terms)
         * so that the Mondrian schema cache has the right key to flush.
         */
        String transformedUri = repositoryCatalogLocator.locate(transformUri(conn.getSchema().getReferenceURI()));

        transformedUri = connectProps.put(RolapConnectionProperties.Catalog.toString(), transformedUri);

        // writing a DynamicSchemaProcessor looks like the way to update schema
        // on the fly
        //  connectProps.put(RolapConnectionProperties.DynamicSchemaProcessor
        //      .toString(), "NONE");

        // To cope with changes in the underlying schema
        connectProps.put(RolapConnectionProperties.UseContentChecksum.toString(), useContentChecksum);

        if (dataSource instanceof JdbcReportDataSource) {
            JdbcReportDataSource jdbcDataSource = (JdbcReportDataSource) dataSource;
            connectProps.put(RolapConnectionProperties.Jdbc.toString(), jdbcDataSource.getConnectionUrl());
            String driverClassName = jdbcDataSource.getDriverClass();
            connectProps.put(RolapConnectionProperties.JdbcDrivers.toString(), driverClassName);

            try {
                // load the driver- may not have been done, mondrian expects it
                log.info("Loading jdbc driver: " + driverClassName);
                jdbcDriverService.register(driverClassName);
            } catch (ClassNotFoundException cnfe) {
                log.error("CANNOT LOAD DRIVER: " + driverClassName);
                throw new JSException(cnfe);
            } catch (Exception e) {
                throw new JSException(e);
            }

            if (jdbcDataSource.getUsername() != null && jdbcDataSource.getUsername().trim().length() > 0) {
                connectProps.put(RolapConnectionProperties.JdbcUser.toString(), jdbcDataSource.getUsername());
            }

            if (jdbcDataSource.getPassword() != null && jdbcDataSource.getPassword().trim().length() > 0) {
                connectProps.put(RolapConnectionProperties.JdbcPassword.toString(), jdbcDataSource.getPassword());
            }

        } else {
            // We have a JNDI data source
            JndiJdbcReportDataSource jndiDataSource = (JndiJdbcReportDataSource) dataSource;

            String jndiURI = "";

            if (jndiDataSource.getJndiName() != null && !jndiDataSource.getJndiName().startsWith("java:")) {
                try {
                    Context ctx = new InitialContext();
                    ctx.lookup("java:comp/env/" + jndiDataSource.getJndiName());
                    jndiURI = "java:comp/env/";
                } catch (NamingException e) {
                    //Added as short time solution due of http://bugzilla.jaspersoft.com/show_bug.cgi?id=26570.
                    //The main problem - this code executes in separate tread (non http).
                    //Jboss 7 support team recommend that you use the non-component environment namespace for such situations.
                    try {
                        Context ctx = new InitialContext();
                        ctx.lookup(jndiDataSource.getJndiName());
                        jndiURI = "";

                    } catch (NamingException ex) {

                    }
                }
            }
            jndiURI = jndiURI + jndiDataSource.getJndiName();

            connectProps.put(RolapConnectionProperties.DataSource.toString(), jndiURI);
        }

        if (log.isDebugEnabled()) {
            log.debug("connection properties prepared: " + connectProps);
        }

        // TODO get from web context and metadata
        // + "RoleXX='California manager';";
        return connectProps;
    }

    /**
     * No transformation of URIs in Community Edition
     * @param uri
     * @return
     */
    protected String transformUri(String uri) {
        return uri;
    }

    /* should something like this be part of the repository api? */
    @Transactional(propagation = Propagation.REQUIRED)
    public Resource dereference(ExecutionContext context, ResourceReference ref) {
        if (ref.isLocal())
            return ref.getLocalResource();
        // resources used by olap objects can be accessible with execute-only perms
        context = ExecutionContextImpl.getRuntimeExecutionContext(context);
        return getRepository().getResource(context, ref.getReferenceURI());
    }

    /**
     * saveResource creates path of folders as necessary and put the resource in
     * the bottommost folder does not update if the target already exists. maybe
     * this can be added to the RepositoryService API?
     */
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public void saveResource(ExecutionContext context, String path, Resource resource) {
        RepositoryService rep = getRepository();

        // check if the target already exists
        String targetUri = path + (path.endsWith(Folder.SEPARATOR) ? "" : Folder.SEPARATOR) + resource.getName();
        if (rep.resourceExists(context, targetUri)) {
            return;
        }

        Folder folder = mkdirs(context, path);
        resource.setParentFolder(folder);
        rep.saveResource(context, resource);
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public Folder mkdirs(ExecutionContext context, String path) {
        RepositoryService rep = getRepository();

        // travel down the elements of the path
        String[] splitPath = path.split(Folder.SEPARATOR);
        String folderName = "/"; // start with root
        Folder parentFolder = null; // root's parent is null
        Folder folder = rep.getFolder(context, folderName);
        for (int i = 0; i < splitPath.length; i++) {
            log.debug("Current path element is " + splitPath[i]);
            if ("".equals(splitPath[i])) {
                continue; // ignore extra slashes
            }
            log.debug("Folder name '" + folderName + "' yields folder '" + folder + "'");
            if (!folderName.equals("/")) {
                folderName += "/";
            }
            folderName += splitPath[i];
            parentFolder = folder; // remember parent
            folder = rep.getFolder(context, folderName);
            if (folder == null) {
                folder = new FolderImpl();
                folder.setName(splitPath[i]);
                folder.setLabel(splitPath[i]);
                folder.setDescription(splitPath[i] + " folder");
                folder.setParentFolder(parentFolder);
                rep.saveFolder(context, folder);
            }
        }
        log.debug("Folder name '" + folderName + "' yields folder '" + folder + "'");
        return folder;
    }

    // PROPERTIES

    private RepositoryService mRepository;

    private String useContentChecksum;

    public RepositoryService getRepository() {
        return mRepository;
    }

    public void setRepository(RepositoryService repository) {
        mRepository = repository;
    }

    public String getUseContentChecksum() {
        return useContentChecksum;
    }

    public void setUseContentChecksum(String useContentChecksum) {
        this.useContentChecksum = useContentChecksum;
    }

    private StaticCharacterEncodingProvider encodingProvider;

    /**
     * returns character encoding provided by jaspersoft
     * @return
     */
    public StaticCharacterEncodingProvider getEncodingProvider() {
        return encodingProvider;
    }

    /**
     * sets character encoding provided by jaspersoft
     * @param encodingProviderIn
     */
    public void setEncodingProvider(StaticCharacterEncodingProvider encodingProviderIn) {
        encodingProvider = encodingProviderIn;
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public ReportDataSourceService createService(ReportDataSource dataSource) {
        ReportDataSourceService dsService;
        if (dataSource instanceof MondrianConnection) {
            MondrianConnection mondrianConnection = (MondrianConnection) dataSource;
            Connection connection = getMondrianConnection(null, mondrianConnection);
            dsService = new MondrianConnectionDataSourceService(connection);
        } else if (dataSource instanceof XMLAConnection) {
            XMLAConnection xmlaConnection = (XMLAConnection) dataSource;
            String tenantSeparator = (tenantService != null) ? tenantService.getUserOrgIdDelimiter() : null;
            if (lacksAuthentication(xmlaConnection)) {
                User user = getCurrentUserDetails();
                dsService = new XmlaConnectionDataSourceService(xmlaConnection, tenantSeparator, user);
            } else {
                dsService = new XmlaConnectionDataSourceService(xmlaConnection, tenantSeparator);
            }
        } else {
            throw new JSException("jsexception.invalid.olap.datasource", new Object[] { dataSource.getClass() });
        }
        return dsService;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public OlapConnection getOlapConnection(ExecutionContext context, String resourceName) {
        OlapClientConnection conn = (OlapClientConnection) getRepository().getResource(context, resourceName);
        return getOlapConnection(context, conn);
    }

    public OlapConnection getOlapConnection(ExecutionContext context, OlapClientConnection conn) {
        Util.PropertyList propList = getOlapConnectProperties(context, conn);

        Properties props = new Properties();
        String driverClass = null;
        String urlPrefix = null;
        for (Pair<String, String> pair : propList) {
            if (pair.getKey().equals(OLAP4J_DRIVER)) {
                driverClass = pair.getValue();
            } else if (pair.getKey().equals(OLAP4J_URL_PREFIX)) {
                urlPrefix = pair.getValue();
            } else {
                props.put(pair.getKey(), pair.getValue());
            }
        }

        //OLAP4J cache configuration
        if (getOLAP4J_CACHE() != null && getOLAP4J_CACHE().length() > 0) {
            props.put("Cache", getOLAP4J_CACHE());
            props.put("Cache.Name", getOLAP4J_CACHE_NAME());
            props.put("Cache.Mode", getOLAP4J_CACHE_MODE());
            props.put("Cache.Timeout", getOLAP4J_CACHE_TIMEOUT());
            props.put("Cache.Size", getOLAP4J_CACHE_SIZE());
        }

        if (log.isDebugEnabled()) {
            StringBuilder sb = new StringBuilder();

            sb.append("OlapConnectionProperties Full Set: ");
            for (Pair<String, String> pair : propList) {
                sb.append("key='" + pair.getKey() + "', val='" + pair.getValue() + "', ");
            }

            sb.append("\nOlapConnectionProperties Driver Set: ");
            for (Object key : props.keySet()) {
                sb.append("key='" + key + "', val='" + props.get(key) + "', ");
            }

            sb.append("\nDriver Class='" + (driverClass == null ? "NULL" : driverClass) + "'");
            sb.append("\n   urlPrefix='" + (urlPrefix == null ? "NULL" : urlPrefix) + "'");

            log.debug(sb.toString());
        }

        // load driver  and Connection
        java.sql.Connection rConnection = null;
        try {
            jdbcDriverService.register(driverClass);
            rConnection = java.sql.DriverManager.getConnection(urlPrefix, props);
        } catch (MondrianException e) {
            String dataSource = (String) props.get(OLAP_CONNECTION_JNDI_DATA_SOURCE);

            if (ExceptionUtils.indexOfThrowable(e, NoInitialContextException.class) > 0 && dataSource != null) {
                Map<String, String> jdbcProperties = jndiFallbackResolver.getJdbcPropertiesMap(dataSource);

                try {
                    // Remove JNDI reference.
                    props.remove(OLAP_CONNECTION_JNDI_DATA_SOURCE);

                    // Add JDBC url, username and password.
                    props.put(OLAP_CONNECTION_JDBC, jdbcProperties.get(JndiFallbackResolver.JDBC_URL));
                    props.put(OLAP_CONNECTION_JDBC_USER, jdbcProperties.get(JndiFallbackResolver.JDBC_USERNAME));
                    props.put(OLAP_CONNECTION_JDBC_PASSWORD,
                            jdbcProperties.get(JndiFallbackResolver.JDBC_PASSWORD));

                    rConnection = java.sql.DriverManager.getConnection(urlPrefix, props);
                } catch (Throwable t) {
                    throw new JSException("Error getting connection from jndi fallback properties.", t);
                }
            } else {
                throw e;
            }
        } catch (Throwable t) {
            throw new JSException("error loading olap4j driver and getting Connection '" + driverClass + "'", t);
        }

        ((OlapConnection) rConnection).setLocale(LocaleContextHolder.getLocale());

        return (OlapConnection) rConnection;
    }

    public XMLATestResult testConnection(ExecutionContext context, XMLAConnection xmlaConnection) {
        try {
            OlapConnectionService service = this;
            OlapConnection connection = service.getOlapConnection(context, xmlaConnection);
            ResultSet rs;
            try {
                rs = connection.getMetaData().getDatabases();
            } catch (Exception e) {
                Throwable ee = e;
                while (ee.getCause() != null && ee.getCause() != ee)
                    ee = ee.getCause();
                String message = ee.getClass().getName() + ": " + ee.getMessage();
                if (ee.getClass().equals(ConnectException.class) || ee.getClass().equals(SocketException.class)
                        || ee.getClass().equals(FileNotFoundException.class)
                        || ee.getClass().equals(UnknownHostException.class)) {
                    return new XMLATestResult(XMLATestCode.URI_CONNECTION_FAILED, message, e);
                } else if (ee.getClass().equals(StringIndexOutOfBoundsException.class)) {
                    return new XMLATestResult(XMLATestCode.BAD_URI, message, e);
                } else if (ee.getClass().equals(IOException.class)) {
                    if (ee.getMessage().contains("401")) {
                        return new XMLATestResult(XMLATestCode.BAD_CREDENTIALS, message, e);
                    } else {
                        return new XMLATestResult(XMLATestCode.BAD_URI, message, e);
                    }
                } else {
                    return new XMLATestResult(XMLATestCode.OTHER, message, e);
                }
            }
            try {
                Schema schema = connection.getOlapSchema();
            } catch (Exception e) {
                if (e.getMessage().contains("No datasource could be found")) {
                    LinkedList<String> options = new LinkedList<String>();
                    try {
                        while (rs.next()) {
                            options.add(rs.getString(1));
                        }
                    } catch (Exception ee) {
                        //do nothing
                    }
                    return new XMLATestResult(XMLATestCode.BAD_DATASOURCE, e.getMessage(),
                            options.toArray(new String[0]), e);
                } else if (e.getMessage().contains("There is no catalog named")) {
                    LinkedList<String> options = new LinkedList<String>();
                    for (Catalog cat : connection.getOlapCatalogs()) {
                        options.add(cat.getName());
                    }
                    return new XMLATestResult(XMLATestCode.BAD_CATALOG, e.getMessage(),
                            options.toArray(new String[0]), e);
                } else {
                    return new XMLATestResult(XMLATestCode.OTHER, e.getMessage(), e);
                }
            }
            return new XMLATestResult(XMLATestCode.OK);
        } catch (Exception e) {
            XMLATestResult result = new XMLATestResult(XMLATestCode.OTHER, e.getMessage(), e);
            return result;
        }
    }

    //
    // Entry point for unit tests
    //
    public mondrian.olap.Connection getOlap4jMondrianConnection(ExecutionContext context, String resourceName) {
        OlapConnection oConn = getOlapConnection(context, resourceName);
        if (oConn != null) {
            return getOlap4jMondrianConnectionFromOlapConnection(oConn);
        }
        return null; //  we should have Exceptioned out before reaching here
    }

    protected mondrian.olap.Connection getOlap4jMondrianConnectionFromOlapConnection(OlapConnection oConn) {
        try {
            return (mondrian.olap.Connection) ((java.sql.Wrapper) oConn).unwrap(mondrian.olap.Connection.class);
        } catch (Exception e) {
            throw new JSException(
                    "could not obtain mondrian.olap.Connection from '" + oConn + "' " + e.getMessage());
        }
    }

    public UserAuthorityService getUserService() {
        return userService;
    }

    public void setUserService(UserAuthorityService userService) {
        this.userService = userService;
    }

    public TenantService getTenantService() {
        return tenantService;
    }

    public void setTenantService(TenantService tenantService) {
        this.tenantService = tenantService;
    }

    /**
     * Searches all members of a Class until a member of the given type
     * is found.
     *
     *  Returns a reference to that Field or NULL if none found
     *
     *  WARNING:  this method will return the FIRST found member of the specified
     *            type.  Be sure that this is what you want.
     *            IF there are multiple members of type 'A', you will get the first one.
     *
     *
     * @param cl
     * @param typeName
     * @return
     */
    public static Field getFieldByTypeName(Class cl, String typeName) {
        // Check we have valid arguments
        assert (cl != null);
        assert (typeName != null);

        final Field fields[] = cl.getDeclaredFields();
        for (int i = 0; i < fields.length; ++i) {
            if (typeName.equals(fields[i].getType().getName())) {

                fields[i].setAccessible(true);
                return fields[i];
            }
        }
        return null;
    }

    public void setJndiFallbackResolver(JndiFallbackResolver jndiFallbackResolver) {
        this.jndiFallbackResolver = jndiFallbackResolver;
    }

    public String getOLAP4J_CACHE() {
        return OLAP4J_CACHE;
    }

    public void setOLAP4J_CACHE(String oLAP4J_CACHE) {
        OLAP4J_CACHE = oLAP4J_CACHE;
    }

    public String getOLAP4J_CACHE_NAME() {
        return OLAP4J_CACHE_NAME;
    }

    public void setOLAP4J_CACHE_NAME(String oLAP4J_CACHE_NAME) {
        OLAP4J_CACHE_NAME = oLAP4J_CACHE_NAME;
    }

    public String getOLAP4J_CACHE_MODE() {
        return OLAP4J_CACHE_MODE;
    }

    public void setOLAP4J_CACHE_MODE(String oLAP4J_CACHE_MODE) {
        OLAP4J_CACHE_MODE = oLAP4J_CACHE_MODE;
    }

    public String getOLAP4J_CACHE_TIMEOUT() {
        return OLAP4J_CACHE_TIMEOUT;
    }

    public void setOLAP4J_CACHE_TIMEOUT(String oLAP4J_CACHE_TIMEOUT) {
        OLAP4J_CACHE_TIMEOUT = oLAP4J_CACHE_TIMEOUT;
    }

    public String getOLAP4J_CACHE_SIZE() {
        return OLAP4J_CACHE_SIZE;
    }

    public void setOLAP4J_CACHE_SIZE(String oLAP4J_CACHE_SIZE) {
        OLAP4J_CACHE_SIZE = oLAP4J_CACHE_SIZE;
    }

    public JdbcDriverService getJdbcDriverService() {
        return jdbcDriverService;
    }

    public void setJdbcDriverService(JdbcDriverService jdbcDriverService) {
        this.jdbcDriverService = jdbcDriverService;
    }
}