/*
 * 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.catalina.session;

import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import org.junit.Assert;
import org.junit.Test;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Session;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
import org.apache.catalina.valves.PersistentValve;

public class TestPersistentManagerIntegration extends TomcatBaseTest {

    /**
     * Wait enough for the system clock to update its value. On some systems
     * (e.g. old Windows) the clock granularity is tens of milliseconds.
     */
    private void waitForClockUpdate() throws InterruptedException {
        long startTime = System.currentTimeMillis();
        int waitTime = 1;
        do {
            Thread.sleep(waitTime);
            waitTime *= 10;
        } while (System.currentTimeMillis() == startTime);
    }

    /**
     * Wait while session access counter has a positive value.
     */
    private void waitWhileSessionIsActive(StandardSession session)
            throws InterruptedException {
        long maxWaitTime = System.currentTimeMillis() + 60000;
        AtomicInteger accessCount = session.accessCount;
        while (accessCount.get() > 0) {
            // Wait until o.a.c.connector.Request.recycle() completes,
            // as it updates lastAccessedTime.
            Assert.assertTrue(System.currentTimeMillis() < maxWaitTime);
            Thread.sleep(200);
        }
    }

    @Test
    public void noSessionCreate_57637() throws IOException, LifecycleException {

        // Setup Tomcat instance
        Tomcat tomcat = getTomcatInstance();

        // No file system docBase required
        StandardContext ctx = (StandardContext) tomcat.addContext("", null);
        ctx.setDistributable(true);

        Tomcat.addServlet(ctx, "DummyServlet", new DummyServlet());
        ctx.addServletMappingDecoded("/dummy", "DummyServlet");

        PersistentManager manager = new PersistentManager();
        TesterStore store = new TesterStore();

        manager.setStore(store);
        manager.setMaxIdleBackup(0);
        manager.setSessionActivityCheck(true);
        ctx.setManager(manager);
        ctx.addValve(new PersistentValve());
        tomcat.start();
        Assert.assertEquals(manager.getActiveSessions(), 0);
        Assert.assertTrue("No sessions managed", manager.getSessionIdsFull().isEmpty());
        Assert.assertEquals(
                "NO_SESSION",
                getUrl(
                        "http://localhost:" + getPort()
                                + "/dummy?no_create_session=true").toString());
        Assert.assertEquals(manager.getActiveSessions(), 0);
        Assert.assertTrue("No sessions where created", manager.getSessionIdsFull().isEmpty());
    }

    @Test
    public void testCreateSessionAndPassivate() throws IOException, LifecycleException, ClassNotFoundException {

        // Setup Tomcat instance
        Tomcat tomcat = getTomcatInstance();

        // No file system docBase required
        StandardContext ctx = (StandardContext) tomcat.addContext("", null);
        ctx.setDistributable(true);

        Tomcat.addServlet(ctx, "DummyServlet", new DummyServlet());
        ctx.addServletMappingDecoded("/dummy", "DummyServlet");

        PersistentManager manager = new PersistentManager();
        TesterStore store = new TesterStore();

        manager.setStore(store);
        manager.setMaxIdleBackup(0);
        manager.setSessionActivityCheck(true);
        ctx.setManager(manager);
        ctx.addValve(new PersistentValve());
        tomcat.start();
        Assert.assertEquals("No active sessions", manager.getActiveSessions(), 0);
        Assert.assertTrue("No sessions managed", manager.getSessionIdsFull().isEmpty());
        String sessionId = getUrl(
                "http://localhost:" + getPort()
                        + "/dummy?no_create_session=false").toString();
        Assert.assertNotNull("Session is stored", store.load(sessionId));
        Assert.assertEquals("All sessions are passivated", manager.getActiveSessions(), 0);
        Assert.assertTrue("One session was created", !manager.getSessionIdsFull().isEmpty());
    }

    @Test
    public void backsUpOnce_56698() throws IOException, LifecycleException,
            InterruptedException {

        // Setup Tomcat instance
        Tomcat tomcat = getTomcatInstance();

        // No file system docBase required
        Context ctx = tomcat.addContext("", null);
        ctx.setDistributable(true);

        Tomcat.addServlet(ctx, "DummyServlet", new DummyServlet());
        ctx.addServletMappingDecoded("/dummy", "DummyServlet");

        PersistentManager manager = new PersistentManager();
        TesterStore store = new TesterStore();

        manager.setStore(store);
        manager.setMaxIdleBackup(0);
        manager.setSessionActivityCheck(true);
        ctx.setManager(manager);
        tomcat.start();
        String sessionId = getUrl("http://localhost:" + getPort() + "/dummy")
                .toString();

        // Note: PersistenceManager.findSession() silently updates
        // session.lastAccessedTime, so call it only once before other work.
        Session session = manager.findSession(sessionId);

        // Wait until request processing ends, as Request.recycle() updates
        // session.lastAccessedTime via session.endAccess().
        waitWhileSessionIsActive((StandardSession) session);

        long lastAccessedTime = session.getLastAccessedTimeInternal();

        // Session should be idle at least for 0 second (maxIdleBackup)
        // to be eligible for persistence, thus no need to wait.

        // Waiting a bit, to catch changes in last accessed time of a session
        waitForClockUpdate();

        manager.processPersistenceChecks();
        Assert.assertEquals(Arrays.asList(sessionId), store.getSavedIds());
        Assert.assertEquals(lastAccessedTime, session.getLastAccessedTimeInternal());

        // session was not accessed, so no save will be performed
        waitForClockUpdate();
        manager.processPersistenceChecks();
        Assert.assertEquals(Arrays.asList(sessionId), store.getSavedIds());
        Assert.assertEquals(lastAccessedTime, session.getLastAccessedTimeInternal());

        // access session
        session.access();
        session.endAccess();

        // session was accessed, so it will be saved once again
        manager.processPersistenceChecks();
        Assert.assertEquals(Arrays.asList(sessionId, sessionId),
                store.getSavedIds());

        // session was not accessed, so once again no save will happen
        manager.processPersistenceChecks();
        Assert.assertEquals(Arrays.asList(sessionId, sessionId),
                store.getSavedIds());
    }

    private static class DummyServlet extends HttpServlet {

        private static final long serialVersionUID = -3696433049266123995L;

        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
                throws ServletException, IOException {
            boolean createSession = !Boolean.parseBoolean(req
                            .getParameter("no_create_session"));
            HttpSession session = req.getSession(createSession);
            if (session == null) {
                resp.getWriter().print("NO_SESSION");
            } else {
                String id = session.getId();
                resp.getWriter().print(id);
            }
        }

    }
}
