/*
* JBoss, a division of Red Hat
* Copyright 2010, Red Hat Middleware, LLC, and individual
* contributors as indicated by the @authors tag. See the
* copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.gatein.wsrp.handler;
import org.gatein.wsrp.consumer.handlers.ProducerSessionInformation;
import javax.xml.namespace.QName;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A WS-level handler to intercept cookies returned from the remote producer responses and provide them back again to the producer when invoking WS operations.
*
* @author <a href="mailto:julien@jboss.org">Julien Viet</a>
* @author <a href="mailto:chris.laprun@jboss.com">Chris Laprun</a>
*/
public class RequestHeaderClientHandler implements SOAPHandler<SOAPMessageContext>
{
private static final ThreadLocal<CurrentInfo> local = new ThreadLocal<CurrentInfo>();
public Set<QName> getHeaders()
{
return null;
}
public boolean handleMessage(SOAPMessageContext soapMessageContext)
{
// outbound message means request
if (Boolean.TRUE.equals(soapMessageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY)))
{
return handleRequest(soapMessageContext);
}
else
{
return handleResponse(soapMessageContext);
}
}
public boolean handleFault(SOAPMessageContext soapMessageContext)
{
return true;
}
public void close(MessageContext messageContext)
{
// nothing to do
}
/**
* Retrieves cookie information from currently held information and adds them to outbound (producer-bound) messages.
*
* @param msgContext the SOAP message context to add cookies to
* @return <code>true</code> to continue processing, <code>false</code> otherwise (defined by {@link javax.xml.ws.handler.Handler#handleMessage(javax.xml.ws.handler.MessageContext)})
*/
public boolean handleRequest(SOAPMessageContext msgContext)
{
final List<String> cookies = getCookiesFromCurrentInfo();
if (cookies.isEmpty())
{
return true;
}
else
{
SOAPMessage message = msgContext.getMessage();
// Legacy JBossWS native approach
final MimeHeaders mimeHeaders = message.getMimeHeaders();
// proper approach through MessageContext.HTTP_REQUEST_HEADERS
List<String> cookieHeaders = getCookieHeaders(msgContext);
for (String cookie : cookies)
{
mimeHeaders.addHeader(CookieUtil.COOKIE, cookie);
cookieHeaders.add(cookie);
}
return true;
}
}
private List<String> getCookieHeaders(SOAPMessageContext msgContext)
{
Map<String, List<String>> httpHeaders = (Map<String, List<String>>)msgContext.get(MessageContext.HTTP_REQUEST_HEADERS);
if (httpHeaders == null)
{
httpHeaders = new HashMap<String, List<String>>();
msgContext.put(MessageContext.HTTP_REQUEST_HEADERS, httpHeaders);
}
List<String> cookieHeaders = httpHeaders.get(CookieUtil.COOKIE);
if (cookieHeaders == null)
{
cookieHeaders = new LinkedList<String>();
httpHeaders.put(CookieUtil.COOKIE, cookieHeaders);
}
return cookieHeaders;
}
public static String createCoalescedCookieFromCurrentInfo()
{
return CookieUtil.coalesceCookies(getCookiesFromCurrentInfo());
}
private static List<String> getCookiesFromCurrentInfo()
{
CurrentInfo info = getCurrentInfo(false);
if (info != null)
{
final ProducerSessionInformation sessionInfo = info.sessionInfo;
if (sessionInfo == null)
{
return Collections.emptyList();
}
final List<String> cookies = new ArrayList<String>(7);
if (sessionInfo.isPerGroupCookies())
{
if (info.groupId == null)
{
throw new IllegalStateException("Was expecting a current group Id...");
}
cookies.addAll(sessionInfo.getGroupCookiesFor(info.groupId));
}
cookies.addAll(sessionInfo.getUserCookies());
return cookies;
}
return Collections.emptyList();
}
/**
* Extracts any cookie from the inbound (provider-issued) message and make this information available to other WSRP components via {@link ProducerSessionInformation}.
*
* @param msgContext the inbound (provider-issued) message from which cookie information is extracted
* @return <code>true</code> to continue processing, <code>false</code> otherwise (defined by {@link javax.xml.ws.handler.Handler#handleMessage(javax.xml.ws.handler.MessageContext)})
*/
public boolean handleResponse(MessageContext msgContext)
{
SOAPMessageContext smc = (SOAPMessageContext)msgContext;
SOAPMessage message = smc.getMessage();
// get cookies
// proper approach through MessageContext.HTTP_RESPONSE_HEADERS
@SuppressWarnings("unchecked")
Map<String, List<String>> httpHeaders = (Map<String, List<String>>)smc.get(MessageContext.HTTP_RESPONSE_HEADERS);
List<String> cookieValues = httpHeaders.get(CookieUtil.SET_COOKIE);
if (cookieValues == null)
{
// try the legacy JBossWS native approach
MimeHeaders mimeHeaders = message.getMimeHeaders();
final String[] cookieHeaders = mimeHeaders.getHeader(CookieUtil.SET_COOKIE);
if (cookieHeaders != null)
{
cookieValues = Arrays.asList(cookieHeaders);
}
}
// if we have cookies...
if (cookieValues != null)
{
String endpointAddress = (String)msgContext.get(BindingProvider.ENDPOINT_ADDRESS_PROPERTY);
if (endpointAddress == null)
{
throw new NullPointerException("Was expecting an endpoint address but none was provided in the MessageContext");
}
URL hostURL;
try
{
hostURL = new URL(endpointAddress);
}
catch (MalformedURLException e)
{
// should not happen
throw new IllegalArgumentException("'" + endpointAddress + "' is not a valid URL for the endpoint address.");
}
// extract their metadata and check that they are valid for the given domain
final List<CookieUtil.Cookie> cookies = CookieUtil.extractCookiesFrom(hostURL, cookieValues);
// retrieve the session information associated with this interaction
CurrentInfo info = getCurrentInfo(true);
ProducerSessionInformation sessionInfo = info.sessionInfo;
// update the current information with the new information
if (sessionInfo.isPerGroupCookies())
{
if (info.groupId == null)
{
throw new IllegalStateException("Was expecting a current group Id...");
}
sessionInfo.setGroupCookiesFor(info.groupId, cookies);
}
else
{
sessionInfo.setUserCookies(cookies);
}
}
// allow other handlers in the chain to process the message
return true;
}
/**
* Updates the information associated with the current interactions with the specified information.
*
* @param groupId the portlet group identifier (if any) associated with the portlet(s) being interacted with
* @param sessionInformation the session metadata to associate with the current interaction
*/
public static void setCurrentInfo(String groupId, ProducerSessionInformation sessionInformation)
{
local.set(new CurrentInfo(groupId, sessionInformation));
}
/** Removes any data associated with the current interaction. */
public static void resetCurrentInfo()
{
local.set(null);
}
/**
* Retrieves the session metadata associated with the current interaction.
*
* @return the session metadata associated with the current interaction or <code>null</code> if none currently exists
*/
public static ProducerSessionInformation getCurrentProducerSessionInformation()
{
CurrentInfo info = getCurrentInfo(false);
if (info != null)
{
return info.sessionInfo;
}
return null;
}
/**
* Retrieves the portlet group identifier, if any, associated with the portlets being interacted with.
*
* @return the portlet group identifier associated with the portlets being interacted with or <code>null</code> if the portlets are not part of a group
*/
public static String getCurrentGroupId()
{
CurrentInfo info = getCurrentInfo(false);
if (info != null)
{
return info.groupId;
}
return null;
}
/**
* Specifies that the portlets being interacted with are part of the portlet group identified by the specified group identifier
*
* @param groupId the group identifier for the group the portlets being interacted with are part of
*/
public static void setCurrentGroupId(String groupId)
{
CurrentInfo currentInfo = local.get();
if (currentInfo == null)
{
throw new IllegalStateException("Cannot set current group id when the current info hasn't been initialized.");
}
currentInfo.groupId = groupId;
}
private static CurrentInfo getCurrentInfo(boolean createIfNeeded)
{
CurrentInfo info = local.get();
if (info == null && createIfNeeded)
{
info = new CurrentInfo(null, new ProducerSessionInformation());
local.set(info);
}
return info;
}
/** Holder for the current (thread-specific) cookie and session information particular to this consumer-producer exchange. */
static class CurrentInfo
{
public CurrentInfo(String groupId, ProducerSessionInformation sessionInfo)
{
this.groupId = groupId;
this.sessionInfo = sessionInfo;
}
/** The WSRP groupId for the portlet being invoked */
String groupId;
/** Session information */
ProducerSessionInformation sessionInfo;
}
}