/*
* 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.wicket.cdi;
import javax.enterprise.context.ContextNotActiveException;
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.inject.Inject;
import org.apache.wicket.Application;
import org.apache.wicket.MetaDataKey;
import org.apache.wicket.Page;
import org.apache.wicket.core.request.handler.BufferedResponseRequestHandler;
import org.apache.wicket.core.request.handler.IPageClassRequestHandler;
import org.apache.wicket.core.request.handler.IPageRequestHandler;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.IRequestHandlerDelegate;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.cycle.IRequestCycleListener;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.lang.Classes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A request cycle listener that takes care of propagating persistent
* conversations.
*
* @see ConversationScoped
*
* @author igor
*/
public class ConversationPropagator implements IRequestCycleListener
{
private static final Logger logger = LoggerFactory.getLogger(ConversationPropagator.class);
private static final MetaDataKey<Boolean> CONVERSATION_STARTED_KEY = new MetaDataKey<Boolean>()
{
private static final long serialVersionUID = 1L;
};
private static final MetaDataKey<String> CONVERSATION_ID_KEY = new MetaDataKey<String>()
{
private static final long serialVersionUID = 1L;
};
public static final String CID = "cid";
/** propagation mode to use */
private final IConversationPropagation propagation;
private final Application application;
@Inject
private Conversation conversation;
/**
* Constructor
*
* @param application
* @param propagation
*/
public ConversationPropagator(Application application, IConversationPropagation propagation)
{
Args.notNull(application, "application");
Args.notNull(propagation, "propagation");
if (propagation == ConversationPropagation.NONE)
{
throw new IllegalArgumentException(
"If propagation is NONE do not set up the propagator");
}
this.application = application;
this.propagation = propagation;
NonContextual.of(ConversationPropagator.class).postConstruct(this);
}
public IConversationPropagation getPropagation()
{
return propagation;
}
@Override
public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler)
{
if (activateForHandler(handler))
{
logger.debug("Activating conversation {}", conversation.getId());
fireOnAfterConversationStarted(cycle);
}
}
private void fireOnAfterConversationStarted(RequestCycle cycle)
{
cycle.setMetaData(CONVERSATION_STARTED_KEY, true);
for (IRequestCycleListener listener : application.getRequestCycleListeners())
{
if (listener instanceof ICdiAwareRequestCycleListener)
{
((ICdiAwareRequestCycleListener)listener).onAfterConversationActivated(cycle);
}
}
}
@Override
public void onRequestHandlerExecuted(RequestCycle cycle, IRequestHandler handler)
{
// propagate current non-transient conversation to the newly scheduled
// page
try
{
if (conversation.isTransient())
{
return;
}
} catch (ContextNotActiveException cnax)
{
logger.debug("There is no active context for the requested scope!", cnax);
return;
}
if (propagation.propagatesVia(handler, getPage(handler)))
{
logger.debug(
"Propagating non-transient conversation {} via page parameters of handler {}",
conversation.getId(), handler);
PageParameters parameters = getPageParameters(handler);
if (parameters != null)
{
parameters.set(CID, conversation.getId());
markPageWithConversationId(handler, conversation.getId());
}
}
}
@Override
public void onUrlMapped(RequestCycle cycle, IRequestHandler handler, Url url)
{
// no need to propagate the conversation to packaged resources, they
// should never change
if (handler instanceof ResourceReferenceRequestHandler)
{
if (((ResourceReferenceRequestHandler)handler).getResourceReference() instanceof PackageResourceReference)
{
return;
}
}
if (conversation.isTransient())
{
return;
}
if (propagation.propagatesVia(handler, getPage(handler)))
{
logger.debug("Propagating non-transient conversation {} via url", conversation.getId());
url.setQueryParameter(CID, conversation.getId());
markPageWithConversationId(handler, conversation.getId());
}
}
@Override
public void onDetach(RequestCycle cycle)
{
if (!Boolean.TRUE.equals(cycle.getMetaData(CONVERSATION_STARTED_KEY)))
{
return;
}
logger.debug("Deactivating conversation {}", conversation.getId());
for (IRequestCycleListener listener : application.getRequestCycleListeners())
{
if (listener instanceof ICdiAwareRequestCycleListener)
{
((ICdiAwareRequestCycleListener)listener).onBeforeConversationDeactivated(cycle);
}
}
}
/**
* Determines whether or not a conversation should be activated fro the
* specified handler. This method is used to filter out conversation
* activation for utility handlers such as the
* {@link BufferedResponseRequestHandler}
*
* @param handler
* @return {@code true} iff a conversation should be activated
*/
protected boolean activateForHandler(IRequestHandler handler)
{
if (handler != null)
{
String handlerClassName = Classes.name(handler.getClass());
if (handler instanceof BufferedResponseRequestHandler)
{
// we do not care about pages that are being rendered from a buffer
return false;
} else if ("org.apache.wicket.protocol.ws.api.WebSocketMessageBroadcastHandler".equals(handlerClassName))
{
return false;
} else if ("org.apache.wicket.protocol.ws.api.WebSocketRequestHandler".equals(handlerClassName)) {
// injection is not supported in web sockets communication
return false;
}
}
return true;
}
public static void markPageWithConversationId(IRequestHandler handler, String cid)
{
Page page = getPage(handler);
if (page != null)
{
page.setMetaData(CONVERSATION_ID_KEY, cid);
}
}
public static String getConversationIdFromPage(Page page)
{
return page.getMetaData(CONVERSATION_ID_KEY);
}
public static void removeConversationIdFromPage(Page page)
{
page.setMetaData(CONVERSATION_ID_KEY, null);
}
/**
* Resolves a page instance from the request handler iff the page instance
* is already created
*
* @param handler
* @return page or {@code null} if none
*/
public static Page getPage(IRequestHandler handler)
{
while (handler instanceof IRequestHandlerDelegate)
{
handler = ((IRequestHandlerDelegate)handler).getDelegateHandler();
}
if (handler instanceof IPageRequestHandler)
{
IPageRequestHandler pageHandler = (IPageRequestHandler)handler;
if (pageHandler.isPageInstanceCreated())
{
return (Page)pageHandler.getPage();
}
}
return null;
}
/**
* Resolves page parameters from a request handler
*
* @param handler
* @return page parameters or {@code null} if none
*/
protected PageParameters getPageParameters(IRequestHandler handler)
{
if (handler instanceof IPageClassRequestHandler)
{
IPageClassRequestHandler pageHandler = (IPageClassRequestHandler)handler;
return pageHandler.getPageParameters();
}
return null;
}
}