/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jetspeed.page.cache;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.security.PrivilegedAction;
import java.util.HashSet;
import java.util.Set;

import javax.security.auth.Subject;

import org.apache.commons.jexl.JexlContext;
import org.apache.commons.jexl.JexlHelper;
import org.apache.commons.jexl.Script;
import org.apache.commons.jexl.ScriptFactory;
import org.apache.jetspeed.components.JetspeedBeanDefinitionFilter;
import org.apache.jetspeed.components.SpringComponentManager;
import org.apache.jetspeed.components.jndi.JetspeedTestJNDIComponent;
import org.apache.jetspeed.page.PageManager;
import org.apache.jetspeed.page.PageManagerTestShared;
import org.apache.jetspeed.security.JSSubject;
import org.apache.jetspeed.security.PrincipalsSet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * DatabasePageManagerServer
 * 
 * @author <a href="mailto:rwatler@apache.org">Randy Watler</a>
 * @version $Id: $
 */
public class DatabasePageManagerServer
{
    protected static Logger log = LoggerFactory.getLogger(DatabasePageManagerServer.class);
    
    // Constants
    
    public static final String SCRIPT_RESULT_LINE_PREFIX = "> ";
    
    // Members
    
    private JetspeedTestJNDIComponent jndiDS;
    private String baseDir;
    private SpringComponentManager scm;
    private PageManager pageManager;
    private JexlContext jexlContext;
    private boolean exit;
    private String user;
    private String groups;
    private String roles;
    private Subject userSubject;
    
    // Life cycle
    
    /**
     * Initialize page manager server instance and script context.
     * 
     * @throws Exception
     */
    public void initialize() throws Exception
    {
        // setup jetspeed test datasource and component manager
        jndiDS = new JetspeedTestJNDIComponent();
        jndiDS.setup();
        final JetspeedBeanDefinitionFilter beanDefinitionFilter = new JetspeedBeanDefinitionFilter("default,jdbcDS");
        final String [] bootConfigurations = new String[]{"boot/datasource.xml"};
        final String [] configurations = new String[]{"database-page-manager.xml", "transaction.xml"};
        baseDir = System.getProperty("basedir");
        if ((baseDir == null) || (baseDir.length() == 0))
        {
            baseDir = System.getProperty("user.dir");
        }
        final String appRoot = baseDir+"/target/test-classes/webapp";
        scm = new SpringComponentManager(beanDefinitionFilter, bootConfigurations, configurations, appRoot, false);
        scm.start();

        // access page manager
        pageManager = (PageManager)scm.getComponent("pageManager");
        
        // create jexl context
        jexlContext = JexlHelper.createContext();
        jexlContext.getVars().put("pageManager", pageManager);
        jexlContext.getVars().put("pageManagerServer", this);
        
        log.info( "DatabasePageManager server initialized");
    }
    
    /**
     * Terminate page manager server instance.
     * 
     * @throws Exception
     */
    public void terminate() throws Exception
    {
        // shutdown page manager
        pageManager.shutdown();

        // tear down jetspeed component manager and test datasource
        scm.stop();
        jndiDS.tearDown();

        log.info( "DatabasePageManager server terminated");
    }
    
    // Implementation
    
    /**
     * Execute a single line script against page manager server context.
     * 
     * @param scriptLine jexl script
     * @return script result line
     */
    public String execute(final String scriptLine)
    {
        // execute script line and return result line
        String resultLine = scriptLine;
        try
        {
            final Script jexlScript = ScriptFactory.createScript(scriptLine);
            final Object result = jexlScript.execute(jexlContext);
            if (result != null)
            {
                resultLine += " -> "+result;
            }
        }
        catch (final Exception e)
        {
            resultLine += " -> "+e;
        }
        return resultLine;
    }

    /**
     * Sets server exit flag.
     */
    public void exit()
    {
        exit = true;
    }
    
    // Data access
    
    /**
     * Get server exit flag.
     * 
     * @return server exit flag
     */
    public boolean isExit()
    {
        return exit;
    }
    
    /**
     * Get user principal name.
     * 
     * @return user principal name
     */
    public String getUser()
    {
        return user;
    }
    
    /**
     * Set user principal name.
     * 
     * @param user user principal name
     */
    public void setUser(String user)
    {
        this.user = user;
        this.userSubject = null;
    }
    
    /**
     * Get user group principal names.
     * 
     * @return CSV list of group principal names
     */
    public String getGroups()
    {
        return groups;
    }

    /**
     * Set user group principal names.
     * 
     * @param groups CSV list of group principal names
     */
    public void setGroups(String groups)
    {
        this.groups = groups;
        this.userSubject = null;
    }

    /**
     * Get user role principal names.
     * 
     * @return CSV list of role principal names
     */
    public String getRoles()
    {
        return roles;
    }

    /**
     * Set user role principal names.
     * 
     * @param roles CSV list of role principal names
     */
    public void setRoles(String roles)
    {
        this.roles = roles;
        this.userSubject = null;
    }

    /**
     * Get or create and cache user subject.
     * 
     * @return user subject
     */
    public Subject getUserSubject()
    {
        if ((userSubject == null) && (user != null))
        {
            Set userPrincipals = new PrincipalsSet();
            userPrincipals.add(new PageManagerTestShared.TestUser(user));
            if (groups != null)
            {
                String [] groupsArray = groups.split(",");
                for (int i = 0; (i < groupsArray.length); i++)
                {
                    userPrincipals.add(new PageManagerTestShared.TestGroup(groupsArray[i].trim()));                                    
                }
            }
            if (roles != null)
            {
                String [] rolesArray = roles.split(",");
                for (int i = 0; (i < rolesArray.length); i++)
                {
                    userPrincipals.add(new PageManagerTestShared.TestRole(rolesArray[i].trim()));                                    
                }
            }
            userSubject = new Subject(true, userPrincipals, new HashSet(), new HashSet());            
        }
        return userSubject;
    }
    
    // Application entry point
    
    /**
     * Server main entry point.
     * 
     * @param args not used
     */
    public static void main(final String [] args)
    {
        try
        {
            // create and initialize server
            final DatabasePageManagerServer server = new DatabasePageManagerServer();
            server.initialize();
            
            // simple server reads script lines from standard
            // input and writes results on standard output
            final BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            final PrintWriter out = new PrintWriter(System.out, true);
            do
            {
                // read single line scripts to execute
                String scriptLine = in.readLine();
                if (scriptLine != null)
                {
                    scriptLine = scriptLine.trim();
                    String resultLine = "";
                    if (scriptLine.length() > 0)
                    {
                        // get user and execute script
                        Subject userSubject = server.getUserSubject();
                        if (userSubject != null)
                        {                            
                            // execute script as user
                            final String executeScriptLine = scriptLine;
                            final String [] executeResultLine = new String[]{null};
                            Exception executeException = (Exception)JSSubject.doAsPrivileged(userSubject, new PrivilegedAction()
                            {
                                public Object run()
                                {
                                    try
                                    {
                                        executeResultLine[0] = server.execute(executeScriptLine);
                                        return null;
                                    }
                                    catch (Exception e)
                                    {
                                        return e;
                                    }
                                    finally
                                    {
                                        JSSubject.clearSubject();
                                    }
                                }
                            }, null);
                            if (executeException != null)
                            {
                                throw executeException;
                            }
                            resultLine = executeResultLine[0];
                        }
                        else
                        {
                            // execute script anonymously
                            resultLine = server.execute(scriptLine);
                        }
                    }

                    // write prefixed single line results
                    out.println(SCRIPT_RESULT_LINE_PREFIX+resultLine);
                }
                else
                {
                    // exit server on input EOF
                    server.exit();
                }
            }
            while (!server.isExit());
            
            // terminate server
            server.terminate();
        }
        catch (final Throwable t)
        {
            log.error( "Unexpected exception: "+t, t);
        }
    }
}
