/*
 * Copyright 2000-2001,2004 The Apache Software Foundation.
 * 
 * Licensed 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.apache.jetspeed.profiler.impl;

import java.security.Principal;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import javax.security.auth.Subject;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jetspeed.components.persistence.store.Filter;
import org.apache.jetspeed.components.persistence.store.LockFailedException;
import org.apache.jetspeed.components.persistence.store.PersistenceStore;
import org.apache.jetspeed.components.persistence.store.Transaction;
import org.apache.jetspeed.profiler.ProfileLocator;
import org.apache.jetspeed.profiler.ProfiledPageContext;
import org.apache.jetspeed.profiler.Profiler;
import org.apache.jetspeed.profiler.ProfilerException;
import org.apache.jetspeed.profiler.rules.PrincipalRule;
import org.apache.jetspeed.profiler.rules.ProfilingRule;
import org.apache.jetspeed.profiler.rules.impl.AbstractProfilingRule;
import org.apache.jetspeed.profiler.rules.impl.PrincipalRuleImpl;
import org.apache.jetspeed.request.RequestContext;
import org.apache.jetspeed.security.SecurityHelper;
import org.apache.jetspeed.security.UserPrincipal;

/**
 * JetspeedProfiler
 * 
 * @deprecated: instead @see JetspeedProfilerImpl
 * @author <a href="mailto:taylor@apache.org">David Sean Taylor </a>
 * @version $Id: JetspeedProfiler.java,v 1.16 2004/11/03 15:59:47 weaver Exp $
 */
public class JetspeedProfiler implements Profiler 
{
    /** Commons logging */
    protected final static Log log = LogFactory.getLog(JetspeedProfiler.class);

    /** The default locator class implementation */
    private Class locatorClass = JetspeedProfileLocator.class;
    /** The default profiled page context class implementation */
    private Class profiledPageContextClass = JetspeedProfiledPageContext.class;
    /** The default principalRule association class implementation */
    private Class principalRuleClass = PrincipalRuleImpl.class;
    /** The base (abstract) profilingRule class implementation */
    private Class profilingRuleClass = AbstractProfilingRule.class;

    /** The configured default rule for this portal */
    private String defaultRule = "j1";

    private String anonymousUser = "guest";

    PersistenceStore persistentStore;

    private Map principalRules = new HashMap();

    public JetspeedProfiler( PersistenceStore persistentStore )
    {
        this.persistentStore = persistentStore;
    }
    
    /**
     * Create a JetspeedProfiler with properties. Expected properties are:
     * 
     * defaultRule = the default profiling rule anonymousUser = the name of the
     * anonymous user storeName = The name of the persistence store component to
     * connect to services.profiler.locator.impl = the pluggable Profile Locator
     * impl services.profiler.principalRule.impl = the pluggable Principal Rule
     * impl services.profiler.profilingRule.impl = the pluggable Profiling Rule
     * impl
     * 
     * @param pContainer
     *            The persistence store container
     * @param properties
     *            Properties for this component described above
     * @throws ClassNotFoundException
     *             if any the implementation classes defined within the
     *             <code>properties</code> argument could not be found.
     */
    public JetspeedProfiler( PersistenceStore persistentStore, Properties properties )
            throws ClassNotFoundException
    {
        this(persistentStore);
        this.defaultRule = properties.getProperty("defaultRule", "j1");
        this.anonymousUser = properties.getProperty("anonymousUser", "guest");
        initModelClasses(properties); // TODO: move this to start()
    }

    private void initModelClasses( Properties properties ) throws ClassNotFoundException
    {
        String modelName = "";

        if ((modelName = properties.getProperty("locator.impl")) != null)
        {
            locatorClass = Class.forName(modelName);
        }
        if ((modelName = properties.getProperty("profiledPageContext.impl")) != null)
        {
            profiledPageContextClass = Class.forName(modelName);
        }
        if ((modelName = properties.getProperty("principalRule.impl")) != null)
        {
            principalRuleClass = Class.forName(modelName);
        }
        if ((modelName = properties.getProperty("profilingRule.impl")) != null)
        {
            profilingRuleClass = Class.forName(modelName);
        }

    }

    public ProfileLocator getProfile(RequestContext context, String locatorName) 
    throws ProfilerException
    {
        // get the principal representing the currently logged on user
        Subject subject = context.getSubject();
        if (subject == null)
        {
            String msg = "Invalid (null) Subject in request pipeline";
            log.error(msg);
            throw new ProfilerException(msg);
        }
        // get the UserPrincipal, finding the first UserPrincipal, or
        // find the first principal if no UserPrincipal isn't available
        Principal principal = SecurityHelper.getBestPrincipal(subject, UserPrincipal.class);
        if (principal == null)
        {
            String msg = "Could not find a principle for subject in request pipeline";
            log.error(msg);
            throw new ProfilerException(msg);
        }

        // find a profiling rule for this principal
        ProfilingRule rule = getRuleForPrincipal(principal, locatorName);
        if (null == rule)
        {
            log.warn("Could not find profiling rule for principal: " + principal);
            rule = this.getDefaultRule();
        }

        if (null == rule)
        {
            String msg = "Couldn't find any profiling rules including default rule for principal " + principal;
            log.error(msg);
            throw new ProfilerException(msg);
        }
        // create a profile locator for given rule
        return rule.apply(context, this);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.jetspeed.profiler.ProfilerService#getProfile(org.apache.jetspeed.request.RequestContext,
     *      org.apache.jetspeed.profiler.rules.ProfilingRule)
     */
    public ProfileLocator getProfile( RequestContext context, ProfilingRule rule )
    {
        // create a profile locator for given rule
        return rule.apply(context, this);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.jetspeed.profiler.ProfilerService#getDefaultRule()
     */
    public ProfilingRule getDefaultRule()
    {
        return lookupProfilingRule(this.defaultRule);
    }

    public ProfilingRule getRuleForPrincipal(Principal principal, String locatorName)
    {
        // lookup the rule for the given principal in our user/rule table
        PrincipalRule pr = lookupPrincipalRule(principal.getName(), locatorName);

        // if not found, fallback to the system wide rule
        if (pr == null)
        {
            return getDefaultRule();
        }

        // Now get the associated rule
        return pr.getProfilingRule();
    }
    
    
    public void setRuleForPrincipal(Principal principal, ProfilingRule rule, String locatorName)
    {
        Transaction tx = persistentStore.getTransaction();
        tx.begin();
  
        Filter filter = persistentStore.newFilter();
        filter.addEqualTo("principalName", principal);
        filter.addEqualTo("locatorName", locatorName);
        Object query = persistentStore.newQuery(principalRuleClass, filter);
        PrincipalRule pr = (PrincipalRule) persistentStore.getObjectByQuery(query);
        if (pr == null)
        {
            pr = new PrincipalRuleImpl(); // TODO: factory
            pr.setPrincipalName(principal.getName());
            pr.setLocatorName(locatorName);
            pr.setProfilingRule(rule);
        }
        try
        {
            pr.setProfilingRule(rule);
            persistentStore.lockForWrite(pr);
        }
        catch (LockFailedException e)
        {
            tx.rollback();
            e.printStackTrace();
            // TODO: throw appropriate exception
        }
        persistentStore.getTransaction().commit();
        principalRules.put(makePrincipalRuleKey(principal.getName(), locatorName), pr);
    }

    private String makePrincipalRuleKey(String principal, String locator)
    {
        return principal + ":" + locator;
    }
    
    /**
     * Helper function to lookup principal rule associations by principal
     * 
     * @param principal
     *            The string representation of the principal name.
     * @return The found PrincipalRule associated with the principal key or null
     *         if not found.
     */
    private PrincipalRule lookupPrincipalRule(String principal, String locatorName)
    {
        PrincipalRule pr = (PrincipalRule) principalRules.get(makePrincipalRuleKey(principal, locatorName));
        if (pr != null)
        {
            return pr;
        }
        Filter filter = persistentStore.newFilter();        
        filter.addEqualTo("principalName", principal);
        filter.addEqualTo("locatorName", locatorName);        
        Object query = persistentStore.newQuery(principalRuleClass, filter);
        pr = (PrincipalRule) persistentStore.getObjectByQuery(query);
        principalRules.put(makePrincipalRuleKey(principal, locatorName), pr);
        return pr;
    }

    /**
     * Helper function to lookup a profiling rule by rule id
     * 
     * @param ruleid
     *            The unique identifier for a rule.
     * @return The found ProfilingRule associated with the rule id or null if
     *         not found.
     */
    private ProfilingRule lookupProfilingRule( String ruleid )
    {
        // TODO: implement caching
        Filter filter = persistentStore.newFilter();
        Object query = persistentStore.newQuery(profilingRuleClass, filter);
        ProfilingRule rule = (ProfilingRule) persistentStore.getObjectByQuery(query);
        return rule;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.jetspeed.profiler.Profiler#createLocator(org.apache.jetspeed.request.RequestContext)
     */
    public ProfileLocator createLocator( RequestContext context )
    {
        try
        {
            ProfileLocator locator = (ProfileLocator) locatorClass.newInstance();
            locator.init(this, context.getPath());
            return locator;
        }
        catch (Exception e)
        {
            log.error("Failed to create locator for " + locatorClass);
        }
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.jetspeed.profiler.Profiler#createProfiledPageContext(java.util.Map)
     */
    public ProfiledPageContext createProfiledPageContext(Map locators)
    {
        try
        {
            ProfiledPageContext pageContext = (ProfiledPageContext) profiledPageContextClass.newInstance();
            pageContext.init(this, locators);
            return pageContext;
        }
        catch (Exception e)
        {
            log.error("Failed to create profiled page context for " + profiledPageContextClass);
        }
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.jetspeed.profiler.ProfilerService#getRules()
     */
    public Collection getRules()
    {
        return persistentStore.getExtent(profilingRuleClass);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.jetspeed.profiler.ProfilerService#getRule(java.lang.String)
     */
    public ProfilingRule getRule( String id )
    {
        Filter filter = persistentStore.newFilter();
        filter.addEqualTo("id", id);
        Object query = persistentStore.newQuery(profilingRuleClass, filter);
        return (ProfilingRule) persistentStore.getObjectByQuery(query);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.jetspeed.profiler.ProfilerService#getAnonymousUser()
     */
    public String getAnonymousUser()
    {
        return this.anonymousUser;
    }

    public String[] getLocatorNamesForPrincipal(Principal principal)
    {
        Filter filter = persistentStore.newFilter();        
        filter.addEqualTo("principalName", principal.getName());
        Object query = persistentStore.newQuery(principalRuleClass, filter);
        Collection result = persistentStore.getCollectionByQuery(query);
        if (result.size() == 0)
        {
            return new String[]{};
        }
        String [] names = new String[result.size()];
        Iterator it = result.iterator();
        int ix = 0;
        while (it.hasNext())
        {
            PrincipalRule pr = (PrincipalRule)it.next();
            names[ix] = pr.getLocatorName();
            ix++;
        }
        return names;
    }
    
    public Collection getRulesForPrincipal(Principal principal)
    {
        Filter filter = persistentStore.newFilter();        
        filter.addEqualTo("principalName", principal.getName());
        Object query = persistentStore.newQuery(principalRuleClass, filter);
        Collection result = persistentStore.getCollectionByQuery(query);
        return result;
    }
    
    
    public Map getProfileLocators(RequestContext context, Principal principal)
    throws ProfilerException
    {
        Map locators = new HashMap();
        Iterator it = getRulesForPrincipal(principal).iterator();
        while (it.hasNext())
        {
            PrincipalRule pr = (PrincipalRule)it.next();
            locators.put(pr.getLocatorName(), getProfile(context, pr.getLocatorName()));   
        }
        return locators;
    }
    
    
    public void storeProfilingRule(ProfilingRule rule)
    throws ProfilerException
    {
        try
        {
            Transaction tx = persistentStore.getTransaction();
            tx.begin();
            persistentStore.makePersistent(rule);
            persistentStore.lockForWrite(rule);
            tx.commit();            
        }
        catch (Exception e)
        {
            throw new ProfilerException("failed to store: " + rule.getId(), e);
        }
    }
    
    public void deleteProfilingRule(ProfilingRule rule)
    throws ProfilerException    
    {
        try
        {
            Transaction tx = persistentStore.getTransaction();
            tx.begin();
            persistentStore.deletePersistent(rule);
            tx.commit();
        }
        catch (Exception e)
        {
            throw new ProfilerException("failed to delete: " + rule.getId(), e);
        }
        
    }
    
    public void storePrincipalRule(PrincipalRule rule)
    throws ProfilerException
    {
        try
        {
            Transaction tx = persistentStore.getTransaction();
            tx.begin();
            persistentStore.makePersistent(rule);
            persistentStore.lockForWrite(rule);
            tx.commit();            
        }
        catch (Exception e)
        {
            throw new ProfilerException("failed to store: " + rule.getLocatorName(), e);
        }        
    }
    
    public void deletePrincipalRule(PrincipalRule rule)
    throws ProfilerException
    {
        try
        {
            Transaction tx = persistentStore.getTransaction();
            tx.begin();
            persistentStore.deletePersistent(rule);
            tx.commit();
        }
        catch (Exception e)
        {
            throw new ProfilerException("failed to delete: " + rule.getLocatorName(), e);
        }        
    }
    public ProfileLocator getDefaultProfile( RequestContext context, String locatorName ) throws ProfilerException
    {
        // TODO Auto-generated method stub
        return null;
    }
    public Map getDefaultProfileLocators( RequestContext context ) throws ProfilerException
    {
        // TODO Auto-generated method stub
        return null;
    }
}
