/*
 * Copyright (C) 2005 - 2011 Jaspersoft Corporation. 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 java.io.IOException;
import java.net.InetAddress;
import java.net.URL;
import java.util.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import mondrian.xmla.DataSourcesConfig;
import mondrian.xmla.XmlaException;
import mondrian.xmla.XmlaHandler;
import mondrian.xmla.impl.DefaultXmlaServlet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import com.jaspersoft.jasperserver.api.common.util.StaticCharacterEncodingProvider;
import com.jaspersoft.jasperserver.api.metadata.common.domain.ResourceLookup;
import com.jaspersoft.jasperserver.api.metadata.common.service.RepositoryService;
import com.jaspersoft.jasperserver.api.metadata.common.service.RepositoryUnsecure;
import com.jaspersoft.jasperserver.api.metadata.olap.domain.MondrianXMLADefinition;
import com.jaspersoft.jasperserver.api.metadata.olap.service.OlapConnectionService;
import com.jaspersoft.jasperserver.api.metadata.olap.service.OlapManagementService;
import com.jaspersoft.jasperserver.api.metadata.user.domain.Tenant;
import com.jaspersoft.jasperserver.api.metadata.user.service.TenantService;
import com.jaspersoft.jasperserver.api.metadata.view.domain.FilterCriteria;
import com.jaspersoft.jasperserver.war.common.JasperServerUtil;

/**
 * @author sbirney
 */

public class XmlaServletImpl extends DefaultXmlaServlet implements
		UpdatableXMLAContainer {

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

	private static ApplicationContext ctx;
	
	private TenantService tenantService;

	private Properties springConfiguration;

    protected String servletURL;
    

	public static ApplicationContext getContext() {
		return ctx;
	}

	public void init(ServletConfig config) throws ServletException {
		log.debug("XmlaServletImpl:init");

		ServletContext servletContext = config.getServletContext();
		ctx = WebApplicationContextUtils
				.getWebApplicationContext(servletContext);
		if (ctx == null) {
			log.error("XmlaServletImpl:init Unable to obtain ApplicationContext from servletContext");
			ctx = new ClassPathXmlApplicationContext(new String[] { "applicationContext.xml" });
			if (ctx == null) {
				log.error("XmlaServletImpl:init Unable to obtain ApplicationContext");
			}
		}

		try {
            springConfiguration = ((Properties) ctx.getBean("springConfiguration"));
		} catch (NoSuchBeanDefinitionException e) {
            springConfiguration = new Properties();
			log.debug("XmlaServletImpl:init : no spring configuration");
			// no tenants, no problem
		}

        String tenantServiceName = "tenantService";
        if (springConfiguration.containsKey("bean.tenantService")) {
            tenantServiceName = springConfiguration.getProperty("bean.tenantService");
        }

		try {
			tenantService = (TenantService) ctx.getBean(tenantServiceName);
		} catch (NoSuchBeanDefinitionException e) {
			log.debug("XmlaServletImpl:init : no tenantService");
			// no tenants, no problem
		}

		super.init(config);
		
		// self registering
		XMLAUpdateListener updateListener = (XMLAUpdateListener) ctx.getBean("xmlaUpdateListener");
		updateListener.registerListener(this);
        
        
        // TODO it is meaningless to generate URLs here, but the XMLServlet requires it
        // Looks like XML/A clients ignore the URL
        try {
            InetAddress local = InetAddress.getLocalHost();

            // We can override the default protocol and port with servlet init parameters

            String defaultProtocol = servletContext
                    .getInitParameter("defaultProtocol");
            if (defaultProtocol == null || defaultProtocol.trim().length() == 0) {
                defaultProtocol = "http";
            }

            String defaultPort = servletContext.getInitParameter("defaultPort");
            if (defaultPort == null || defaultPort.trim().length() == 0) {
                defaultPort = "-1";
            }
            int port = Integer.parseInt(defaultPort);

            URL root = servletContext.getResource("/");
            // Looks like the path will be /localhost/webapp

            int pastHost = root.getPath().indexOf("/", 1);
            String path = root.getPath().substring(pastHost,
                    root.getPath().length());

            servletURL = (new URL(defaultProtocol,
                    local.getCanonicalHostName(), port, path)).toString()
                    + "xmla";
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
	}

	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		try {
			// js:i18n set servlet character encoding
			if (getConnectionService() != null) {
				this.charEncoding = 
					((StaticCharacterEncodingProvider)ctx.getBean("encodingProvider")).getCharacterEncoding();
				log.debug("doPost:charEncoding set to " + this.charEncoding);
			}
			super.doPost(request, response);
		} catch (Throwable t) {
			log.error("XMLA Servlet Error, ROOT CAUSE:");
			log.error(XmlaException.getRootCause(t).getStackTrace());
			throw new ServletException(t);
		}
	}

	protected void handleFault(HttpServletResponse response,
			byte[][] responseSoapParts, Phase phase, Throwable t) {
		log.error("XMLA FAULT!!!");
		log.error(XmlaException.getRootCause(t).getStackTrace());
		super.handleFault(response, responseSoapParts, phase, t);
	}

	protected DataSourcesConfig.DataSources makeDataSources(
			ServletConfig servletConfig) {
		log.debug("makeDataSources");
		//RepositoryService rep = getRepository();
		RepositoryService rep = (RepositoryService) ctx.getBean("unsecureRepositoryService");


		DataSourcesConfig.DataSources datasources = new DataSourcesConfig.DataSources();

		// Use findResource to avoid filtering with security
		FilterCriteria f = FilterCriteria
				.createFilter(MondrianXMLADefinition.class);
		ResourceLookup[] lookups = rep.findResource(JasperServerUtil
				.getExecutionContext(), f);

		if (lookups == null || lookups.length == 0) {
			log.debug("No XML/A Connections available");
			return datasources;
		}

		//datasources.dataSources = new DataSourcesConfig.DataSource[lookups.length];
		//DataSourcesConfig.DataSource[] ds = datasources.dataSources;
		List dsList = new ArrayList(); 

		for (int i = 0; i < lookups.length; i++) {
			
			// disqualify org template resources
			if (lookups[i].getURIString().contains(TenantService.ORG_TEMPLATE)) {
				log.info("Disqualifying from XMLA pool : " + lookups[i].getURIString());
				continue;
			}
			
            // to avoid security checkings, we need to use the interface RepositoryUnsecure
            MondrianXMLADefinition def = (MondrianXMLADefinition) ((RepositoryUnsecure) rep)
                    .getResourceUnsecure(null, lookups[i].getURIString());

            DataSourcesConfig.DataSource d = createDSConfigOutOfDefinition(def);

			if (log.isDebugEnabled()) {
				log.debug("loading DataSource name=" + d.name + ", info="
						+ d.dataSourceInfo + ", url=" + d.url);
			}

			//ds[i] = d;
			dsList.add(d);
		}
		datasources.dataSources = (DataSourcesConfig.DataSource[])
			dsList.toArray(new DataSourcesConfig.DataSource[dsList.size()]);
		return datasources;
	}
    
    protected DataSourcesConfig.DataSource createDSConfigOutOfDefinition(MondrianXMLADefinition def) {
        DataSourcesConfig.DataSource d = new DataSourcesConfig.DataSource();

        d.description = def.getDescription();

        d.url = servletURL;

        d.providerName = "Mondrian";
        d.providerType = DataSourcesConfig.DataSource.PROVIDER_TYPE_MDP;

        // This is really about requiring roles

        d.authenticationMode = DataSourcesConfig.DataSource.AUTH_MODE_UNAUTHENTICATED;

        d.name = "Provider=" + d.providerName + ";DataSource="
                + def.getCatalog() + ";";
        if (tenantService != null) {
            Tenant tenant = tenantService.getTenantBasedOnRepositoryUri(null, def.getURIString());
            if (tenant != null) {
                d.name += "TenantID=" + tenant.getId() + ";";
            }
        }
        
        d.dataSourceInfo = d.name;

        DataSourcesConfig.Catalogs cs = new DataSourcesConfig.Catalogs();
        DataSourcesConfig.Catalog c = new DataSourcesConfig.Catalog();
        c.name = def.getCatalog();
        //c.setDataSource(d); //XmlaHandler.java constructor does this for us
        cs.catalogs = new DataSourcesConfig.Catalog[1];
        cs.catalogs[0] = c;
        d.catalogs = cs;
        c.definition = "JASPERSERVER";
        
        return d;
    }

	/**
	 * property: xmlaHandler
	 */
	protected synchronized XmlaHandler getXmlaHandler() {
		if (this.xmlaHandler == null) {
			log.debug("getXmlaHandler");
			this.xmlaHandler = new XmlaHandlerImpl(this.dataSources,
					this.catalogLocator, getRepository(),
					getConnectionService(),
                                        getOlapManagementService());
		}
		return this.xmlaHandler;
	}

	/**
	 * property: repository
	 */
	private RepositoryService mRepository;

	public RepositoryService getRepository() {
		if (mRepository == null) {
			log.debug("getRepository");

            String repositoryServiceName = "repositoryService";
            if (springConfiguration.containsKey("bean.repositoryService")) {
                repositoryServiceName = springConfiguration.getProperty("bean.repositoryService");
            }

			mRepository = (RepositoryService) ctx.getBean(repositoryServiceName);
			if (mRepository == null) {
				log.error("repositoryService not available in context: " + ctx);
			}
		}
		return mRepository;
	}

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

	/**
	 * property: olapConnectionService
	 */
	private OlapConnectionService mConnectionService;

	public OlapConnectionService getConnectionService() {
		if (mConnectionService == null) {
			log.debug("getConnectionService");
			mConnectionService = (OlapConnectionService) ctx
					.getBean("olapConnectionService");
			if (mConnectionService == null) {
				log.error("repositoryService not available in context: " + ctx);
			}
		}
		return mConnectionService;
	}

	public void setConnectionService(OlapConnectionService cs) {
		mConnectionService = cs;
	}

    private OlapManagementService olapManagementService;

    public OlapManagementService getOlapManagementService() {
        if (olapManagementService == null) {
            log.debug("getOlapManagementService");
            olapManagementService = (OlapManagementService) ctx
                            .getBean("olapManagementService");
            if (olapManagementService == null) {
                    log.error("olapManagementService not available in context: " + ctx);
            }
        }
        return olapManagementService;
    }

    public void setOlapManagementService(OlapManagementService olapManagementService) {
        this.olapManagementService = olapManagementService;
    }

	public synchronized void updateXMLAConnection(MondrianXMLADefinition oldDef, MondrianXMLADefinition newDef) {
        DataSourcesConfig.DataSource[] dss = this.dataSources.dataSources;
        List dsList = new ArrayList(Arrays.asList(dss));
        
	    if (oldDef != null) {
            // undeploy the old definition
            DataSourcesConfig.DataSource oldDS = createDSConfigOutOfDefinition(oldDef);
            for (Iterator iter = dsList.iterator(); iter.hasNext(); ) {
                DataSourcesConfig.DataSource d = (DataSourcesConfig.DataSource) iter.next();
                if (d.getDataSourceInfo().equals(oldDS.getDataSourceInfo())) {
                    iter.remove();
                    break;
                }
            }
        }
        if (newDef != null) {
            // deploy the new definition
            // we assume that it is validated already, 
            // and we don't need to check for duplicates
            dsList.add(createDSConfigOutOfDefinition(newDef));
        }
        
        this.dataSources.dataSources = (DataSourcesConfig.DataSource[])
            dsList.toArray(new DataSourcesConfig.DataSource[dsList.size()]);
	}
    
}
