/*
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.Conversation;
import javax.enterprise.inject.spi.Bean;
import javax.servlet.http.HttpSession;
import org.apache.webbeans.config.OpenWebBeansConfiguration;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.context.ConversationContext;
import org.apache.webbeans.context.SessionContext;
import org.apache.webbeans.conversation.ConversationManager;
import org.apache.webbeans.exception.WebBeansException;
import org.apache.webbeans.logger.WebBeansLoggerFacade;
import org.apache.webbeans.spi.FailOverService;
import org.apache.webbeans.web.context.SessionContextManager;
import org.apache.webbeans.web.context.WebContextsService;
/**
* Default implementation of the {@link FailOverService}.
*/
public class DefaultOwbFailOverService implements FailOverService
{
private static final Logger LOGGER = WebBeansLoggerFacade.getLogger(DefaultOwbFailOverService.class);
public static final String CONFIG_IS_SUPPORT_FAILOVER = "org.apache.webbeans.web.failover.issupportfailover";
public static final String CONFIG_IS_SUPPORT_PASSIVATE = "org.apache.webbeans.web.failover.issupportpassivation";
public static final String CONFIG_RESOURCES_SERIALIZATION_HANDLER = "org.apache.webbeans.web.failover.resources.serialization.handler.v10";
public static final String ATTRIBUTE_SESSION_CONTEXT = "sessionContext";
public static final String ATTRIBUTE_CONVERSATION_CONTEXT_MAP = "conversatzionContextMap";
private static final String JVM_ID = UUID.randomUUID().toString() + "_" + System.currentTimeMillis();
private final WebBeansContext webBeansContext = WebBeansContext.currentInstance();
private ThreadLocal<Boolean> passivation = new ThreadLocal<Boolean>();
private boolean supportFailOver;
private boolean supportPassivation;
private SerializationHandlerV10 handler;
public DefaultOwbFailOverService()
{
OpenWebBeansConfiguration config = webBeansContext.getOpenWebBeansConfiguration();
String value;
value = config.getProperty(CONFIG_IS_SUPPORT_FAILOVER);
if (value != null && value.equalsIgnoreCase("true"))
{
supportFailOver = true;
}
value = config.getProperty(CONFIG_IS_SUPPORT_PASSIVATE);
if (value != null && value.equalsIgnoreCase("true"))
{
supportPassivation = true;
}
if (supportFailOver || supportPassivation)
{
value = config.getProperty(CONFIG_RESOURCES_SERIALIZATION_HANDLER);
if (value != null)
{
try
{
handler = (SerializationHandlerV10) Class.forName(value).newInstance();
}
catch (Exception e)
{
LOGGER.log(Level.SEVERE, "DefaultOwbFailOverService could not instanciate: [" + value + "]", e);
}
}
}
if (LOGGER.isLoggable(Level.FINE))
{
LOGGER.log(Level.FINE, "IsSupportFailOver: [{0}]", String.valueOf(supportFailOver));
LOGGER.log(Level.FINE, "IsSupportPassivation: [{0}]", String.valueOf(supportPassivation));
}
}
@Override
public void sessionIsIdle(HttpSession session)
{
if (session != null)
{
FailOverBag bag = (FailOverBag) session.getAttribute(FailOverBag.SESSION_ATTRIBUTE_NAME);
if (bag == null)
{
bag = new FailOverBag(session.getId(), getJvmId());
}
bag.setSessionInUse(false);
storeBeansInFailOverBag(bag, session);
addFailOverBagToSession(bag, session);
addActivationListenerToSession(session);
}
passivation.remove();
passivation.set(null);
}
@Override
public void sessionIsInUse(HttpSession session)
{
if (session != null)
{
FailOverBag bag = (FailOverBag) session.getAttribute(FailOverBag.SESSION_ATTRIBUTE_NAME);
if (bag != null)
{
bag.setSessionInUse(true);
}
addActivationListenerToSession(session);
}
}
@Override
public void sessionDidActivate(HttpSession session)
{
FailOverBag bag = (FailOverBag) session.getAttribute(FailOverBag.SESSION_ATTRIBUTE_NAME);
if (bag != null)
{
if (bag.isSessionInUse())
{
if (LOGGER.isLoggable(Level.FINE))
{
LOGGER.log(Level.FINE, "Skip restore beans for session [" + bag.getSessionId() + "] because session is in use.");
}
}
else
{
if (LOGGER.isLoggable(Level.FINE))
{
LOGGER.log(Level.FINE, "Restore beans for session [{0}]", session.getId());
}
restoreBeansFromFailOverBag(bag, session);
}
}
}
@Override
public void sessionWillPassivate(HttpSession session)
{
sessionIsIdle(session);
passivation.set(true);
}
/**
* Adds the {@link FailOverSessionActivationListener} to the current {@link HttpSession}.
* It must not be manually registered when we store it as session attribute.
*
* @param session The current {@link HttpSession}.
*/
protected void addActivationListenerToSession(HttpSession session)
{
if (session.getAttribute(FailOverSessionActivationListener.SESSION_ATTRIBUTE_NAME) == null)
{
session.setAttribute(FailOverSessionActivationListener.SESSION_ATTRIBUTE_NAME, new FailOverSessionActivationListener());
}
}
/**
* Store the {@link FailOverBag} as attribute to the current {@link HttpSession}.
* So when the session is fail over to other JVM or local disk, the
* attribute could also be serialized.
*
* @param bag The {@link FailOverBag}.
* @param session The current {@link HttpSession}.
*/
protected void addFailOverBagToSession(FailOverBag bag, HttpSession session)
{
try
{
session.setAttribute(FailOverBag.SESSION_ATTRIBUTE_NAME, bag);
if (LOGGER.isLoggable(Level.FINE))
{
LOGGER.log(Level.FINE, "Successfully added FailOverBag to session [" + bag.getSessionId() + "].");
}
}
catch (Exception e)
{
String message = "Could not add FailOverBag to session [" + bag.getSessionId() + "].";
LOGGER.log(Level.SEVERE, message, e);
throw new WebBeansException(message, e);
}
}
/**
* Stores the session and conversation contexts in the {@link FailOverBag}.
*
* @param bag The {@link FailOverBag}.
* @param session The current {@link HttpSession}.
*/
protected void storeBeansInFailOverBag(FailOverBag bag, HttpSession session)
{
// store the session context
SessionContextManager sessionManager =
((WebContextsService) webBeansContext.getContextsService()).getSessionContextManager();
SessionContext sessionContext = sessionManager.getSessionContextWithSessionId(session.getId());
bag.put(ATTRIBUTE_SESSION_CONTEXT, sessionContext);
// store all conversation contexts
ConversationManager conversationManager = webBeansContext.getConversationManager();
bag.put(ATTRIBUTE_CONVERSATION_CONTEXT_MAP, conversationManager.getConversationMapWithSessionId(session.getId()));
if (LOGGER.isLoggable(Level.FINE))
{
LOGGER.log(Level.FINE, "Beans for session [" + bag.getSessionId() + "] successfully stored in FailOverBag.");
}
}
/**
* Restores the session and conversation contexts from the given {@link FailOverBag}.
*
* @param bag The {@link FailOverBag}.
* @param session The current {@link HttpSession}.
*/
@SuppressWarnings("unchecked")
protected void restoreBeansFromFailOverBag(FailOverBag bag, HttpSession session)
{
try
{
// restore session context
SessionContext sessionContext = (SessionContext) bag.get(ATTRIBUTE_SESSION_CONTEXT);
if (sessionContext != null)
{
SessionContextManager sessionManager =
((WebContextsService) webBeansContext.getContextsService()).getSessionContextManager();
sessionManager.addNewSessionContext(session.getId(), sessionContext);
sessionContext.setActive(true);
}
// restore conversation contexts
Map<Conversation, ConversationContext> conversationContextMap =
(Map<Conversation, ConversationContext>) bag.get(ATTRIBUTE_CONVERSATION_CONTEXT_MAP);
if (conversationContextMap != null && !conversationContextMap.isEmpty())
{
ConversationManager conversationManager = webBeansContext.getConversationManager();
Iterator<Conversation> iterator = conversationContextMap.keySet().iterator();
while (iterator.hasNext())
{
Conversation conversation = iterator.next();
ConversationContext context = conversationContextMap.get(conversation);
conversationManager.addConversationContext(conversation, context);
}
}
if (LOGGER.isLoggable(Level.FINE))
{
LOGGER.log(Level.FINE, "Beans for session [" + bag.getSessionId() + "] from [" + bag.getJvmId() + "] successfully restored.");
}
}
catch (Exception e)
{
String message = "Could not restore beans for session [" + bag.getSessionId()
+ "] from [" + bag.getJvmId() + "]";
LOGGER.log(Level.SEVERE, message, e);
throw new WebBeansException(message, e);
}
}
/**
* Except the EJB remote stub, it is hard to handle other types of
* resources. Here we delegate serialization/deserialization to the
* application provided SerializationHandler.
*/
@Override
public Object handleResource(Bean<?> bean, Object resourceObject, ObjectInput in, ObjectOutput out)
{
if (handler != null)
{
return handler.handleResource(bean, resourceObject, in, out,
(isPassivation()) ? SerializationHandlerV10.TYPE_PASSIVATION : SerializationHandlerV10.TYPE_FAILOVER);
}
return NOT_HANDLED;
}
/**
* Get object input stream. Note, the stream should support deserialize
* javassist objects.
*
* @deprecated as we do not use Javassist anymore this is not needed anymore!
*
* @return custom object input stream.
*/
@Override
public ObjectInputStream getObjectInputStream(InputStream in) throws IOException
{
return new ObjectInputStream(in);
}
/**
* Get object output stream. Note, the stream should support deserialize
* javassist objects.
*
* @deprecated as we do not use Javassist anymore this is not needed anymore!
*
* @return custom object output stream.
*/
@Override
public ObjectOutputStream getObjectOutputStream(OutputStream out) throws IOException
{
return new ObjectOutputStream(out);
}
@Override
public String getJvmId()
{
return JVM_ID;
}
@Override
public boolean isSupportFailOver()
{
return supportFailOver;
}
@Override
public void enableFailOverSupport(boolean supportFailOver)
{
this.supportFailOver = supportFailOver;
}
@Override
public boolean isSupportPassivation()
{
return supportPassivation;
}
@Override
public void enablePassivationSupport(boolean supportPassivation)
{
this.supportPassivation = supportPassivation;
}
public boolean isPassivation()
{
if (passivation.get() == null)
{
passivation.set(false);
}
return passivation.get();
}
}