/*
 * 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.webbeans.web.failover.tests;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Set;

import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.servlet.http.HttpSession;

import org.apache.webbeans.context.ConversationContext;
import org.apache.webbeans.conversation.ConversationImpl;
import org.apache.webbeans.conversation.ConversationManager;
import org.apache.webbeans.spi.ConversationService;
import org.apache.webbeans.spi.FailOverService;
import org.apache.webbeans.web.failover.DefaultOwbFailOverService;
import org.apache.webbeans.web.failover.FailOverBag;
import org.apache.webbeans.web.failover.FailOverSessionActivationListener;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class DefaultOwbFailOverServiceTest extends AbstractUnitTest
{
    private static final String SESSION_SCOPED_SAMPLE_TEXT = "session_sample";
    private static final String CONVERSATION_SCOPED_SAMPLE_TEXT = "conversatzion_sample";

    @Before
    public void before()
    {
        Collection<Class<?>> classes = new ArrayList<Class<?>>();
        classes.add(ConversationScopedBean.class);
        classes.add(SessionScopedBean.class);

        startContainer(classes);
    }

    @After
    public void after()
    {
        shutDownContainer();
    }

    /**
     * Tests that the {@link FailOverBag} and {@link FailOverSessionActivationListener} will be added to the session.
     */
    @Test
    public void sessionIsIdle()
    {
        // ----- setup
        HttpSession session = new MockHttpSession();

        DefaultOwbFailOverService failoverService =
                (DefaultOwbFailOverService) getWebBeansContext().getService(FailOverService.class);

        failoverService.enableFailOverSupport(true);
        failoverService.enablePassivationSupport(true);



        // ----- execute
        failoverService.sessionIsIdle(session);



        // ----- assert
        FailOverSessionActivationListener listener = (FailOverSessionActivationListener)
                session.getAttribute(FailOverSessionActivationListener.SESSION_ATTRIBUTE_NAME);
        FailOverBag failOverBag = (FailOverBag)
                session.getAttribute(FailOverBag.SESSION_ATTRIBUTE_NAME);

        Assert.assertNotNull(listener);
        Assert.assertNotNull(failOverBag);
        Assert.assertFalse(failOverBag.isSessionInUse());
        Assert.assertFalse(failoverService.isPassivation());
    }

    /**
     * Tests that <code>sessionInUse</code> will be set and that the
     * {@link FailOverSessionActivationListener} will be added to the session.
     */
    @Test
    public void sessionIsInUse()
    {
        // ----- setup
        HttpSession session = new MockHttpSession();

        // put bag manually to session
        FailOverBag failOverBag = new FailOverBag(session.getId(), "");
        session.setAttribute(FailOverBag.SESSION_ATTRIBUTE_NAME, failOverBag);

        DefaultOwbFailOverService failoverService =
                (DefaultOwbFailOverService) getWebBeansContext().getService(FailOverService.class);

        failoverService.enableFailOverSupport(true);
        failoverService.enablePassivationSupport(true);



        // ----- execute
        failoverService.sessionIsInUse(session);



        // ----- assert
        FailOverSessionActivationListener listener = (FailOverSessionActivationListener)
                session.getAttribute(FailOverSessionActivationListener.SESSION_ATTRIBUTE_NAME);

        Assert.assertNotNull(listener);
        Assert.assertTrue(failOverBag.isSessionInUse());
        Assert.assertFalse(failoverService.isPassivation());
    }

    /**
     * Tests that the {@link FailOverBag} and {@link FailOverSessionActivationListener} will be added to the session.
     */
    @Test
    public void sessionWillPassivate()
    {
        // ----- setup
        HttpSession session = new MockHttpSession();

        DefaultOwbFailOverService failoverService =
                (DefaultOwbFailOverService) getWebBeansContext().getService(FailOverService.class);

        failoverService.enableFailOverSupport(true);
        failoverService.enablePassivationSupport(true);



        // ----- execute
        failoverService.sessionWillPassivate(session);



        // ----- assert
        FailOverSessionActivationListener listener = (FailOverSessionActivationListener)
                session.getAttribute(FailOverSessionActivationListener.SESSION_ATTRIBUTE_NAME);
        FailOverBag failOverBag = (FailOverBag)
                session.getAttribute(FailOverBag.SESSION_ATTRIBUTE_NAME);

        Assert.assertNotNull(listener);
        Assert.assertNotNull(failOverBag);
        Assert.assertFalse(failOverBag.isSessionInUse());
        Assert.assertTrue(failoverService.isPassivation());
    }

    /**
     * Tests that the session context will be stored and restored
     * successfully from the {@link FailOverBag}.
     * 
     * @throws Exception When store/restore the session context fails.
     */
    @Test
    public void restoreSessionContext() throws Exception
    {
        // ----- setup
        HttpSession session = new MockHttpSession();

        DefaultOwbFailOverService failoverService =
                (DefaultOwbFailOverService) getWebBeansContext().getService(FailOverService.class);

        failoverService.enableFailOverSupport(true);
        failoverService.enablePassivationSupport(true);



        // ----- execute
        getWebBeansContext().getContextsService().startContext(SessionScoped.class, session);

        // set sample text
        getInstance(SessionScopedBean.class).setText(SESSION_SCOPED_SAMPLE_TEXT);

        // store beans in session
        failoverService.sessionIsIdle(session);

        // serialize / deserialize and store deserialized bag back to session
        FailOverBag failOverBag;
        failOverBag = (FailOverBag) session.getAttribute(FailOverBag.SESSION_ATTRIBUTE_NAME);
        failOverBag = (FailOverBag) serialize(failOverBag);

        session.setAttribute(FailOverBag.SESSION_ATTRIBUTE_NAME, failOverBag);

        // remove sample text
        getInstance(SessionScopedBean.class).setText(null);

        getWebBeansContext().getContextsService().endContext(SessionScoped.class, session);

        // restore beans from session
        failoverService.sessionDidActivate(session);

        getWebBeansContext().getContextsService().startContext(SessionScoped.class, session);



        // ----- assert
        Assert.assertEquals(SESSION_SCOPED_SAMPLE_TEXT, getInstance(SessionScopedBean.class).getText());
    }

    /**
     * Tests that the conversation contexts will be stored and restored
     * successfully from the {@link FailOverBag}.
     * 
     * @throws Exception When store/restore the session context fails.
     */
    @Test
    public void restoreConversationContexts() throws Exception
    {
        // ----- setup
        HttpSession session = new MockHttpSession();

        DefaultOwbFailOverService failoverService =
                (DefaultOwbFailOverService) getWebBeansContext().getService(FailOverService.class);

        failoverService.enableFailOverSupport(true);
        failoverService.enablePassivationSupport(true);



        // ----- execute
        // create conversation
        ConversationContext conversationContext = new ConversationContext();
        conversationContext.setActive(true);

        Conversation conversation = new ConversationImpl(session.getId(), getWebBeansContext());
        conversation.begin();

        ConversationManager conversationManager = getWebBeansContext().getConversationManager();
        conversationManager.addConversationContext(conversation, conversationContext);

        getWebBeansContext().registerService(ConversationService.class, new MockConversationService(session, conversation));

        // set sample text
        getInstance(ConversationScopedBean.class, conversationContext).setText(CONVERSATION_SCOPED_SAMPLE_TEXT);

        // store beans in session
        failoverService.sessionIsIdle(session);

        // serialize / deserialize and store deserialized bag back to session
        FailOverBag failOverBag;
        failOverBag = (FailOverBag) session.getAttribute(FailOverBag.SESSION_ATTRIBUTE_NAME);
        failOverBag = (FailOverBag) serialize(failOverBag);

        session.setAttribute(FailOverBag.SESSION_ATTRIBUTE_NAME, failOverBag);

        // remove sample text
        getInstance(ConversationScopedBean.class, conversationContext).setText(null);

        getWebBeansContext().getContextsService().endContext(ConversationScoped.class, conversationContext);

        // restore beans from session
        failoverService.sessionDidActivate(session);



        // ----- assert
        // get and activate conversation
        Map<Conversation, ConversationContext> conversationMap =
                conversationManager.getConversationMapWithSessionId(session.getId());

        Conversation propogatedConversation =
                conversationManager.getPropogatedConversation(conversation.getId(), session.getId());

        ConversationContext propogatedConversationContext =  conversationMap.get(propogatedConversation);
        propogatedConversationContext.setActive(true);

        ConversationScopedBean beanInstance = getInstance(ConversationScopedBean.class, propogatedConversationContext);

        Assert.assertEquals(CONVERSATION_SCOPED_SAMPLE_TEXT, beanInstance.getText());
        Assert.assertTrue(beanInstance.isBeanManagerNotNull());
    }

    /**
     * Tests that the {@link FailOverSessionActivationListener} is serializable.
     *
     * @throws Exception If serialization fails.
     */
    @Test
    public void serializeSessionActivationListener() throws Exception
    {
        serialize(new FailOverSessionActivationListener());
    }

    private Serializable serialize(Serializable serializable) throws IOException, ClassNotFoundException
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);

        oos.writeObject(serializable);
        oos.close();

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);

        Object deserializedObject = ois.readObject();

        ois.close();

        return (Serializable) deserializedObject;
    }

    public <T> T getInstance(Class<T> type, Context context)
    {
        Set<Bean<?>> beans = getBeanManager().getBeans(type);
        @SuppressWarnings("unchecked")
        Bean<T> bean = (Bean<T>) getBeanManager().resolve(beans);
        CreationalContext<T> creationalContext = getBeanManager().createCreationalContext(bean);

        return context.get(bean, creationalContext);
    }
}
