/****************************************************************************** * 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 } }