/******************************************************************************
* JBoss, a division of Red Hat *
* Copyright 2011, 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.web.redirect;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.exoplatform.container.ExoContainer;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.container.xml.ValueParam;
import org.exoplatform.portal.application.PortalRequestHandler;
import org.exoplatform.portal.mop.SiteKey;
import org.exoplatform.portal.mop.SiteType;
import org.exoplatform.portal.url.PortalURLContext;
import org.exoplatform.web.ControllerContext;
import org.exoplatform.web.WebRequestHandler;
import org.exoplatform.web.url.URLFactoryService;
import org.exoplatform.web.url.navigation.NavigationResource;
import org.exoplatform.web.url.navigation.NodeURL;
import org.gatein.common.logging.Logger;
import org.gatein.common.logging.LoggerFactory;
import org.gatein.web.redirect.api.RedirectKey;
import org.gatein.web.redirect.api.RedirectType;
import org.gatein.web.redirect.api.SiteRedirectService;
import org.picocontainer.Startable;
/**
* @author <a href="mailto:mwringe@redhat.com">Matt Wringe</a>
* @version $Revision$
*/
public class RedirectRequestHandler extends WebRequestHandler implements Startable {
protected static Logger log = LoggerFactory.getLogger(RedirectRequestHandler.class);
protected static boolean debug = log.isDebugEnabled();
// The handler name to use
public static final String HANDLER_NAME = "siteRedirect";
// The path to the device detection page
protected String browserDetectionPath;
// The service to perform the redirection logic
protected SiteRedirectService deviceRedirectionService;
protected URLFactoryService urlFactory;
// Flag if we have already tried to detect capabilities of the browser
public static final String DEVICE_DETECTION_ATTEMPTED = "gtn.device.detectionAttempted";
// The initial URI that was requested
public static final String INITIAL_URI = "gtn.redirect.initialURI";
// TODO: the flag shouldn't exist here, it needs to exist somewhere else in the 'api' section
public static final String REDIRECT_FLAG = "gtn_redirect";
public RedirectRequestHandler(InitParams params, SiteRedirectService service, URLFactoryService urlFactory) {
this.deviceRedirectionService = service;
this.urlFactory = urlFactory;
ValueParam browserDectionectUrl = params.getValueParam("browser.detection.path");
if (browserDectionectUrl != null) {
browserDetectionPath = browserDectionectUrl.getValue();
}
}
@Override
public String getHandlerName() {
return HANDLER_NAME;
}
@Override
public boolean execute(ControllerContext context) throws Exception {
HttpServletRequest request = context.getRequest();
HttpServletResponse response = context.getResponse();
String originRequestPath = context.getParameter(PortalRequestHandler.REQUEST_PATH);
SiteKey originSite = getOriginSiteKey(context);
if (originRequestPath != null && originRequestPath.equalsIgnoreCase("null")) {
originRequestPath = null;
}
if (debug) {
log.debug("Site Redirect being checked on [" + originSite.getName() + "], with type [" + originSite.getTypeName()
+ "], and request path [" + originRequestPath + "]");
}
String redirectFlagValue = request.getParameter(REDIRECT_FLAG);
if (redirectFlagValue != null && !redirectFlagValue.isEmpty()) {
SiteKey redirectSiteKey = new SiteKey(originSite.getType(), redirectFlagValue);
RedirectKey redirectKey = RedirectKey.redirect(redirectSiteKey.getName());
return performRedirect(originSite, redirectKey, originRequestPath, context, true);
}
String referer = request.getHeader("Referer");
String siteURL = request.getRequestURL().substring(0,
request.getRequestURL().length() - request.getServletPath().length());
if (referer != null && referer.startsWith(siteURL)
&& (context.getRequest().getSession(true).getAttribute(DEVICE_DETECTION_ATTEMPTED) == null)) {
if (debug) {
log.debug("Redirect being requested with a referer from the portal site. Do not attempt redirect and follow link. Referer : " + referer);
}
return false;
}
RedirectKey redirectSite = getRedirect(originSite, request, context);
if (redirectSite != null) {
// a redirect has already been set, use it
// do the redirect
return performRedirect(originSite, redirectSite, originRequestPath, context, false);
} else {
// no redirect set yet, we need to check if a redirect is requested or not
Map<String, String> deviceProperties = null;
String userAgentString = request.getHeader("User-Agent");
if (debug) {
log.debug("Found user-agent string : " + userAgentString);
}
// we only care if this exists or not, no need to set it to anything other than Object
Object attemptedDeviceDetection = context.getRequest().getSession(true).getAttribute(DEVICE_DETECTION_ATTEMPTED);
if (attemptedDeviceDetection != null) {
deviceProperties = getDeviceProperties(request);
context.getRequest().getSession().removeAttribute(DEVICE_DETECTION_ATTEMPTED);
if (debug) {
log.debug("Found device properties : " + deviceProperties);
}
}
redirectSite = deviceRedirectionService.getRedirectSite(originSite.getName(), userAgentString, deviceProperties);
if (redirectSite == null || redirectSite.getType() == RedirectType.NOREDIRECT) {
if (debug) {
log.debug("Redirect returned is null or NO_REDIRECT_DETECTED. Setting NO_REDIRECT for this user");
}
setRedirect(originSite, RedirectKey.noRedirect(), request, response, context);
return false;
} else if (redirectSite.getType() == RedirectType.NEEDDEVICEINFO) {
if (attemptedDeviceDetection == null) {
if (debug) {
log.debug("Need browser properties detection. Redirecting to BrowserDetectionPage : "
+ browserDetectionPath);
}
request.getSession().setAttribute(DEVICE_DETECTION_ATTEMPTED, true);
performRedirectToDeviceDetector(request, response);
return true;
} else {
log.warn("DeviceDetectionService returned NEED_BROWSER_DETECTION but the browser has already attempted detection. Setting no redirect.");
setRedirect(originSite, RedirectKey.noRedirect(), request, response, context);
return false;
}
} else {
// the service gave us a redirection site to use, use it.
if (debug) {
log.debug("Redirect for origin site " + originSite.getName() + " is being set to : " + redirectSite);
}
return performRedirect(originSite, redirectSite, originRequestPath, context, false);
}
}
}
protected RedirectKey getRedirect(SiteKey origin, HttpServletRequest request, ControllerContext context) {
ExoContainer container = ExoContainerContext.getCurrentContainer();
RedirectCookieService redirectCookieService = (RedirectCookieService) container
.getComponentInstanceOfType(RedirectCookieService.class);
return redirectCookieService.getRedirect(origin, request);
}
protected void setRedirect(SiteKey origin, RedirectKey redirect, HttpServletRequest request, HttpServletResponse response,
ControllerContext context) {
ExoContainer container = ExoContainerContext.getCurrentContainer();
RedirectCookieService redirectCookieService = (RedirectCookieService) container
.getComponentInstanceOfType(RedirectCookieService.class);
// Determine the URL for the site so we can use this for the cookie path
PortalURLContext urlContext = new PortalURLContext(context, SiteKey.portal(origin.getName()));
NodeURL url = urlFactory.newURL(NodeURL.TYPE, urlContext);
String path = url.setResource(new NavigationResource(SiteType.PORTAL, origin.getName(), "")).toString();
// We have to check for the '/' at the end of the path since the portal could be accessed under /portal/classic or
// /portal/classic/
// Removing the tailing slash means both urls will see the cookie.
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
Cookie redirectCookie = redirectCookieService.createCookie(origin.getName(), redirect, path);
response.addCookie(redirectCookie);
// In order to make sure we don't create an infinite redirect loop, we should update the cookie for the redirect site to
// specify that site is the prefered site and no redirect should be performed
if (redirect.getType() != RedirectType.NOREDIRECT && redirect.getRedirect() != null) {
// Determine the URL for the redirect site so we can use this for the cookie path
String redirectPath = url.setResource(new NavigationResource(SiteType.PORTAL, redirect.getRedirect(), ""))
.toString();
// We have to check for the '/' at the end of the path since the portal could be accessed under /portal/classic or
// /portal/classic/
// Removing the tailing slash means both urls will see the cookie.
if (redirectPath.endsWith("/")) {
redirectPath = redirectPath.substring(0, redirectPath.length() - 1);
}
Cookie removeRedirectCookie = redirectCookieService.createCookie(redirect.getRedirect(), RedirectKey.noRedirect(),
redirectPath);
response.addCookie(removeRedirectCookie);
}
}
protected Map<String, String> getDeviceProperties(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap != null) {
Map<String, String> deviceProperties = new HashMap<String, String>();
for (String key : parameterMap.keySet()) {
if (key.startsWith("gtn.device.")) {
deviceProperties.put(key.substring("gtn.device.".length()), request.getParameter(key));
}
}
return deviceProperties;
} else {
return null;
}
}
protected boolean performRedirect(SiteKey origin, RedirectKey redirect, String requestPath, ControllerContext context,
boolean forceRedirect) throws IOException {
// If we have a no-redirect type, don't do anything and return null
if (redirect.getType() == RedirectType.NOREDIRECT) {
if (debug) {
log.debug("Using NoRedirect for site " + redirect + " with request path :" + requestPath);
}
return false;
} else {
if (debug) {
log.debug("Attempting redirect to site " + redirect + " with request path :" + requestPath);
}
String redirectLocation = deviceRedirectionService.getRedirectPath(origin.getName(), redirect.getRedirect(),
requestPath);
if (forceRedirect && redirectLocation == null) {
redirectLocation = requestPath;
}
if (redirectLocation != null) {
if (debug) {
log.debug("RedirectPath set to : " + redirectLocation);
}
setRedirect(origin, redirect, context.getRequest(), context.getResponse(), context);
// create the new redirect url
SiteKey siteKey = new SiteKey(SiteType.PORTAL, redirect.getRedirect());
PortalURLContext urlContext = new PortalURLContext(context, siteKey);
NodeURL url = urlFactory.newURL(NodeURL.TYPE, urlContext);
if (redirectLocation.startsWith("/")) {
redirectLocation = redirectLocation.substring(1);
}
String s = url.setResource(new NavigationResource(SiteType.PORTAL, redirect.getRedirect(), redirectLocation))
.toString();
HttpServletResponse response = context.getResponse();
HttpServletRequest request = context.getRequest();
// Add in the query string, if any.
String queryString = request.getQueryString();
if (request.getQueryString() != null) {
// remove the redirect flag from the query string
if (queryString.contains(REDIRECT_FLAG + "=")) {
queryString = queryString.substring(0, queryString.lastIndexOf(REDIRECT_FLAG));
}
if (s.endsWith("/")) {
s = s.substring(0, s.length() - 1);
}
if (queryString != null && !queryString.isEmpty()) {
s += "?" + queryString;
}
}
// set the redirect
if (debug) {
log.debug("Redirecting to : " + s);
}
response.sendRedirect(response.encodeRedirectURL(s));
return true;
} else {
if (debug) {
log.debug("Did not get a node match for redirecting to site [" + redirect + "] with requestPath ["
+ requestPath + "]. Cannot perform redirect.");
}
return false;
}
}
}
protected void performRedirectToDeviceDetector(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String initialURI = request.getRequestURI();
// add back in any query strings to the initialURI
if (request.getQueryString() != null) {
if (initialURI.endsWith("/")) {
initialURI = initialURI.substring(0, initialURI.length() - 1);
}
initialURI += "?" + request.getQueryString();
}
request.getSession().setAttribute(INITIAL_URI, initialURI);
request.setAttribute(INITIAL_URI, initialURI);
request.getRequestDispatcher(browserDetectionPath).forward(request, response);
}
protected SiteKey getOriginSiteKey(ControllerContext context) {
String originSiteName = context.getParameter(PortalRequestHandler.REQUEST_SITE_NAME);
String originSiteTypeString = context.getParameter(PortalRequestHandler.REQUEST_SITE_TYPE);
SiteType originSiteType;
if (originSiteTypeString.equals(SiteType.GROUP.getName())) {
originSiteType = SiteType.GROUP;
} else if (originSiteTypeString.equals(SiteType.USER.getName())) {
originSiteType = SiteType.USER;
} else {
originSiteType = SiteType.PORTAL;
}
return new SiteKey(originSiteType, originSiteName);
}
@Override
protected boolean getRequiresLifeCycle() {
return true;
}
@Override
public void start() {
// required because of eXo kernel
}
@Override
public void stop() {
// required because of eXo kernel
}
}