package com.atlassian.stash.internal.hazelcast;

import com.atlassian.hazelcast.serialization.OsgiSafeStreamSerializer;
import com.atlassian.johnson.Johnson;
import com.atlassian.johnson.event.Event;
import com.atlassian.stash.internal.cluster.NodePassivationException;
import com.atlassian.stash.internal.johnson.JohnsonUtils;
import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.OutOfMemoryHandler;
import com.hazelcast.spi.impl.SerializationServiceSupport;
import org.springframework.beans.factory.config.AbstractFactoryBean;

import javax.servlet.ServletContext;

/**
 * A factory bean for hazelcast which checks whether the application was passivated as part of the construction
 * of the {@link HazelcastInstance}
 * <p>
 * <b>Warning:</b> This bean uses setter injection because constructor injection can cause issues with Spring's
 * type detection for FactoryBeans. Spring needs to instantiate FactoryBeans so it can call getObjectType, and
 * using constructor injection can cause irreconcilable circular dependencies. Using setter injection allows
 * circular dependencies to be automatically reconciled so that type detection can be applied correctly.
 *
 * @since 3.4
 */
public class HazelcastFactoryBean extends AbstractFactoryBean<HazelcastInstance> {

    private Config config;
    private ServletContext servletContext;
    private OsgiSafeStreamSerializer osgiSafeStreamSerializer;

    @Override
    public Class<?> getObjectType() {
        return HazelcastInstance.class;
    }

    public void setConfig(Config config) {
        this.config = config;
    }

    public void setOsgiSafeStreamSerializer(OsgiSafeStreamSerializer osgiSafeStreamSerializer) {
        this.osgiSafeStreamSerializer = osgiSafeStreamSerializer;
    }

    public void setOutOfMemoryHandler(OutOfMemoryHandler outOfMemoryHandler) {
        Hazelcast.setOutOfMemoryHandler(outOfMemoryHandler);
    }

    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    @Override
    protected HazelcastInstance createInstance() {
        HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(config);
        checkNotPassivated(hazelcast);

        if (osgiSafeStreamSerializer != null) {
            osgiSafeStreamSerializer.setSerializationService(
                    ((SerializationServiceSupport) hazelcast).getSerializationService());
        }

        return hazelcast;
    }

    private void checkNotPassivated(HazelcastInstance hazelcast) {
        for (Event event : Johnson.getEventContainer(servletContext).getEvents()) {
            if (JohnsonUtils.EVENT_TYPE_NODE_PASSIVATED.equals(event.getKey().getType())) {
                hazelcast.shutdown();
                throw new NodePassivationException();
            }
        }
    }
}
