/*
* Weblounge: Web Content Management System
* Copyright (c) 2003 - 2011 The Weblounge Team
* http://entwinemedia.com/weblounge
*
* This program 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
* of the License, or (at your option) any later version.
*
* This program 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 program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package ch.entwine.weblounge.common.impl.request;
import ch.entwine.weblounge.common.impl.language.LanguageImpl;
import ch.entwine.weblounge.common.impl.language.LanguageUtils;
import ch.entwine.weblounge.common.impl.url.WebUrlImpl;
import ch.entwine.weblounge.common.language.Language;
import ch.entwine.weblounge.common.language.UnknownLanguageException;
import ch.entwine.weblounge.common.request.RequestFlavor;
import ch.entwine.weblounge.common.request.WebloungeRequest;
import ch.entwine.weblounge.common.security.User;
import ch.entwine.weblounge.common.site.Environment;
import ch.entwine.weblounge.common.site.Site;
import ch.entwine.weblounge.common.url.UrlUtils;
import ch.entwine.weblounge.common.url.WebUrl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;
/**
* This class is a wrapper to the <code>HttpServletRequest</code> with weblounge
* specific functionality enhancements, e. g. to get access to the requested
* site or language.
*/
public class WebloungeRequestImpl extends HttpServletRequestWrapper implements WebloungeRequest {
/** Logging facility */
private Logger logger = LoggerFactory.getLogger(WebloungeRequestImpl.class);
/** The language extraction regular expression */
private static final Pattern LANG_EXTRACTOR_REGEX = Pattern.compile("_([a-zA-Z]+)\\.[\\w\\- ]+$");
/** The request counter */
private static long requestCounter = 0L;
/** The request identifier */
protected String id = null;
/** Target site of this request */
protected Site site = null;
/** The site servlet */
protected Servlet siteServlet = null;
/** User of this request */
protected User user = null;
/** Language used for this site and request */
protected Language language = null;
/** Language found in the session when the request started */
protected Language sessionLanguage = null;
/** Target url */
protected WebUrlImpl url = null;
/** Url that was originally requested */
protected WebUrlImpl requestedUrl = null;
/** The request environment */
protected Environment environment = null;
/**
* Creates a new wrapper for <code>request</code>.
*
* @param request
* the request to wrap.
* @param servlet
* the servlet used to serve content out of the request's site
* @param environment
* the environment
*/
public WebloungeRequestImpl(HttpServletRequest request, Servlet servlet,
Environment environment) {
super(request);
HttpSession session = request.getSession();
if (session != null)
this.sessionLanguage = (Language) session.getAttribute(LANGUAGE);
if (environment == null)
throw new IllegalArgumentException("Environment must not be null");
this.environment = environment;
this.siteServlet = servlet;
}
/**
* Creates a new wrapper for <code>request</code>.
*
* @param request
* the request to wrap.
* @param environment
* the environment
*/
public WebloungeRequestImpl(HttpServletRequest request,
Environment environment) {
this(request, null, environment);
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.request.WebloungeRequest#getEnvironment()
*/
public Environment getEnvironment() {
return environment;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.request.WebloungeRequest#getLanguage()
*/
public Language getLanguage() {
// Has the language been cached?
if (language != null)
return language;
if (site == null)
throw new IllegalStateException("Site has not been set");
// The language might very well be encoded as part of the path, so asking
// for the url might give us the language for free.
if (url == null)
url = (WebUrlImpl) getUrl();
// There is no url without a path, so we can safely assume that we will
// get an object back form getUrl().
language = url.getLanguage();
// Take a look at the url, where language information might be encoded,
// e. g. index_en.xml
if (language == null) {
Matcher m = LANG_EXTRACTOR_REGEX.matcher(getRequestURI());
if (m.find()) {
String languageCandidate = m.group(1);
try {
language = LanguageUtils.getLanguage(languageCandidate);
logger.trace("Selected language " + language + " from request uri");
} catch (UnknownLanguageException e) {
logger.trace("'{}' does not represent a langauge", languageCandidate);
}
}
}
// Get hold of the session
HttpSession session = getSession(true);
// Extract the language from the session (a.k.a an earlier request). Then
// make sure the language was put there for the current site.
if (language == null && session != null) {
language = (Language) session.getAttribute(LANGUAGE);
}
// If the url didn't contain language information, or referenced a language
// that the site doesn't support, let's go for the user's browser
// preferences
if (language == null) {
Enumeration<?> localeEnum = getLocales();
while (localeEnum.hasMoreElements()) {
String languageId = ((Locale) localeEnum.nextElement()).getLanguage();
language = site.getLanguage(languageId);
if (language != null) {
logger.trace("Selected language " + languageId + " from browser preferences");
break;
}
}
}
// Still no valid language? Let's go with the site default.
if (language == null) {
language = site.getDefaultLanguage();
logger.trace("Selected default site language " + language);
}
// This really looks like a configuration disaster!
if (language == null) {
language = new LanguageImpl(Locale.getDefault());
logger.trace("Selected default system language " + language);
}
// Store the current selection in the session
if (language != null && session != null)
session.setAttribute(LANGUAGE, language);
return language;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.request.WebloungeRequest#getSessionLanguage()
*/
public Language getSessionLanguage() {
if (sessionLanguage == null)
return getLanguage();
return sessionLanguage;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.request.WebloungeRequest#getSite()
*/
public Site getSite() {
if (site == null)
throw new IllegalStateException("Site has not been set");
return site;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.request.WebloungeRequest#getUrl()
*/
public WebUrl getUrl() {
if (url != null)
return url;
if (site == null)
throw new IllegalStateException("Site has not been set");
// Let's create a url. The constructor will try to populate as many fields
// on the url as possible, including flavor, language and version which
// might all be encoded in the path.
this.url = new WebUrlImpl(site, getRequestURI());
this.requestedUrl = url;
return url;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.request.WebloungeRequest#getRequestedUrl()
*/
public WebUrl getRequestedUrl() {
if (requestedUrl != null)
return requestedUrl;
if (site == null)
throw new IllegalStateException("Site has not been set");
// If the requested url has not been stored so far, it will anyway be equal
// to what getUrl() returns
requestedUrl = (WebUrlImpl) getUrl();
return requestedUrl;
}
/**
* Sets this request's user.
*
* @param user
* the user
*/
public void setUser(User user) {
this.user = user;
}
/**
* Sets this request's language.
*
* @param language
* the language
*/
public void setLanguage(Language language) {
this.language = language;
this.getSession().setAttribute(LANGUAGE, language);
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.request.WebloungeRequest#getUser()
*/
public User getUser() {
return user;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.request.WebloungeRequest#getVersion()
*/
public long getVersion() {
if (url != null)
return url.getVersion();
return ((WebUrlImpl) getUrl()).getVersion();
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.request.WebloungeRequest#getFlavor()
*/
public RequestFlavor getFlavor() {
RequestFlavor flavor = null;
if (url != null)
flavor = url.getFlavor();
else
flavor = ((WebUrlImpl) getUrl()).getFlavor();
// TODO: Look at accepts-header (text/json, text/xml, text/...)
return flavor != null ? flavor : RequestFlavor.ANY;
}
/**
* Tells the request which site it is serving. This call updates the site in
* the user's session, since it's being used by {@link #getUser()} and other
* methods.
*
* @param site
* the site
*/
public void init(Site site) {
this.site = site;
// Update the request counter
id = Long.toString(requestCounter);
if (requestCounter == Long.MAX_VALUE)
requestCounter = 0L;
else
requestCounter++;
// Handle changes to the users session that might be required should he/she
// be surfing on another site
HttpSession session = getSession(false);
if (session == null)
return;
Site oldSite = (Site) session.getAttribute(SITE);
if (!site.equals(oldSite)) {
clearSession();
}
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletRequestWrapper#getRequestDispatcher(java.lang.String)
*/
@Override
public RequestDispatcher getRequestDispatcher(String path) {
final String servletPath = UrlUtils.concat("/weblounge-sites/", site.getIdentifier());
if (siteServlet != null && path.startsWith(servletPath)) {
return new RequestDispatcher() {
public void include(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
siteServlet.service(request, response);
}
public void forward(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
siteServlet.service(request, response);
}
};
} else {
return super.getRequestDispatcher(path);
}
}
/**
* Method to reset this request object, forcing it to release any cached
* information.
* <p>
* Note that you should only set <code>clearSession</code> to
* <code>true</code> if you want to get rid of cached information to force a
* change in the way the current user is treated, e. g. if he/she has been
* logged out.
* <p>
* The following things are stored in the session
* <ul>
* <li>The current site</li>
* <li>The selected language</li>
* </ul>
*
* @param clearSession
* <code>true</code> to remove cached information as well
*/
public void reset(boolean clearSession) {
site = null;
user = null;
language = null;
url = null;
requestedUrl = null;
if (clearSession) {
clearSession();
}
}
/**
* Utility method used to clear the attributes stored by the url in the user's
* session.
*/
private void clearSession() {
HttpSession session = getSession(false);
if (session == null)
return;
session.removeAttribute(LANGUAGE);
session.setAttribute(SITE, site);
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object o) {
if (o != null && o instanceof WebloungeRequestImpl) {
return ((WebloungeRequestImpl) o).id.equals(id);
}
return false;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return id.hashCode();
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#toString()
*/
public String toString() {
if (url != null)
return url + " (" + id + ")";
else
return getRequestURI();
}
}