Author: cbrisson
Date: Wed Aug 12 08:26:50 2009
New Revision: 803412
URL: http://svn.apache.org/viewvc?rev=803412&view=rev
Log:
added Accept-Language header handling to BrowserTool
Modified:
velocity/tools/trunk/examples/showcase/WEB-INF/tools.xml
velocity/tools/trunk/examples/showcase/browser.vm
velocity/tools/trunk/src/main/java/org/apache/velocity/tools/view/BrowserTool.java
velocity/tools/trunk/src/test/java/org/apache/velocity/tools/test/blackbox/ViewToolsTests.java
Modified: velocity/tools/trunk/examples/showcase/WEB-INF/tools.xml
URL:
http://svn.apache.org/viewvc/velocity/tools/trunk/examples/showcase/WEB-INF/tools.xml?rev=803412&r1=803411&r2=803412&view=diff
==============================================================================
--- velocity/tools/trunk/examples/showcase/WEB-INF/tools.xml (original)
+++ velocity/tools/trunk/examples/showcase/WEB-INF/tools.xml Wed Aug 12
08:26:50 2009
@@ -28,6 +28,11 @@
<tool key="text" bundles="resources,otherStuff"/>
<tool key="params" locale="en_US"/>
</toolbox>
+
+ <toolbox scope="session" locale="en_US">
+ <tool class="org.apache.velocity.tools.view.BrowserTool"
languagesFilter="en"/>
+ </toolbox>
+
<toolbox scope="application">
<tool key="date" format="yyyy-MM-dd" depth="2" skip="month"/>
<tool key="convert" dateFormat="yyyy-MM-dd"/>
Modified: velocity/tools/trunk/examples/showcase/browser.vm
URL:
http://svn.apache.org/viewvc/velocity/tools/trunk/examples/showcase/browser.vm?rev=803412&r1=803411&r2=803412&view=diff
==============================================================================
--- velocity/tools/trunk/examples/showcase/browser.vm (original)
+++ velocity/tools/trunk/examples/showcase/browser.vm Wed Aug 12 08:26:50 2009
@@ -161,5 +161,9 @@
#demo( 'browser' 'Dom1' $desc )
#demo( 'browser' 'Dom2' $desc )
#demo( 'browser' 'Javascript' $desc )
+#demo1('browser' 'setLanguagesFilter' 6 'setter for a coma-separated list of
weapp languages filter')
+#demo( 'browser' 'languagesFilter' 'coma-separated list of weapp languages
filter')
+#set( $desc = 'Returns the preferred language tag from Accept-Header, filtered
by values given to the LanguageFilter param' )
+#demo( 'browser' 'preferredLanguage' $desc)
#demoCustom( 'browser' )
</table>
Modified:
velocity/tools/trunk/src/main/java/org/apache/velocity/tools/view/BrowserTool.java
URL:
http://svn.apache.org/viewvc/velocity/tools/trunk/src/main/java/org/apache/velocity/tools/view/BrowserTool.java?rev=803412&r1=803411&r2=803412&view=diff
==============================================================================
---
velocity/tools/trunk/src/main/java/org/apache/velocity/tools/view/BrowserTool.java
(original)
+++
velocity/tools/trunk/src/main/java/org/apache/velocity/tools/view/BrowserTool.java
Wed Aug 12 08:26:50 2009
@@ -22,8 +22,12 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
+import java.util.*;
import javax.servlet.http.HttpServletRequest;
+import org.apache.velocity.runtime.log.Log;
import org.apache.velocity.tools.Scope;
+import org.apache.velocity.tools.ConversionUtils;
+import org.apache.velocity.tools.generic.FormatConfig;
import org.apache.velocity.tools.config.DefaultKey;
import org.apache.velocity.tools.config.InvalidScope;
@@ -31,8 +35,8 @@
* <p>browser-sniffing tool (session or request scope requested, session
scope advised).</p>
* <p></p>
* <p><b>Usage:</b></p>
- * <p>BrowserTool defines properties that are used to test the client browser,
operating system, device...
- * Apart from properties related to versioning, all properties are
booleans.</p>
+ * <p>BrowserTool defines properties that are used to test the client browser,
operating system, device, language...
+ * Apart from properties related to browser version and language, all
properties are booleans.</p>
* <p>The following properties are available:</p>
* <ul>
* <li><i>Versioning:</i>version majorVersion minorVersion geckoVersion</li>
@@ -46,9 +50,14 @@
* <li><i>Devices:</i>palm audrey iopener wap blackberry</li>
* <li><i>Features:</i>javascript css css1 css2 dom0 dom1 dom2</li>
* <li><i>Special:</i>robot (true if the page is requested by a robot, i.e.
when one of the following properties is true:
- * wget getright yahoo altavista lycos infoseek lwp webcrawler linkexchange
slurp google java)
+ * wget getright yahoo altavista lycos infoseek lwp webcrawler linkexchange
slurp google java)</li>
+ * <li>Language: preferredLanguageTag (a string like 'en', 'da', 'en-US',
...), preferredLocale (a java Locale)</li>
* </ul>
*
+ * <p>Language properties are filtered by the languagesFilter tool param, if
present. If no matching language is found, or if there is no
+ * matching language, the tools defaut locale (or the first value of
languagesFilter) is returned.
+ * Their value is guarantied to belong to the set provided in languagesFilter,
if any.</p>
+ *
* Thanks to Lee Semel ([email protected]), the author of the HTTP::BrowserDetect
Perl module.
* See also:
* * http://www.zytrax.com/tech/web/browser_ids.htm
@@ -61,10 +70,13 @@
*/
@DefaultKey("browser")
@InvalidScope(Scope.APPLICATION)
-public class BrowserTool implements java.io.Serializable
+public class BrowserTool extends FormatConfig implements java.io.Serializable
{
private static final long serialVersionUID = 1734529350532353339L;
+ protected Log LOG;
+
+ /* User-Agent header variables */
private String userAgent = null;
private String version = null;
private int majorVersion = -1;
@@ -73,6 +85,89 @@
private int geckoMajorVersion = -1;
private int geckoMinorVersion = -1;
+ private static Pattern genericVersion = Pattern.compile(
+ "/"
+ /* Version starts with a slash */
+ +
+ "([A-Za-z]*"
+ /* Eat any letters before the major version */
+ +
+ "( [\\d]* )"
+ /* Major version number is every digit before the first dot */
+ + "\\." /* The first dot */
+ +
+ "( [\\d]* )"
+ /* Minor version number is every digit after the first dot */
+ + "[^\\s]*)" /* Throw away the remaining */
+ , Pattern.COMMENTS);
+ private static Pattern firefoxVersion = Pattern.compile(
+ "/"
+ +
+ "(( [\\d]* )"
+ /* Major version number is every digit before the first dot */
+ + "\\." /* The first dot */
+ +
+ "( [\\d]* )"
+ /* Minor version number is every digit after the first dot */
+ + "[^\\s]*)" /* Throw away the remaining */
+ , Pattern.COMMENTS);
+ private static Pattern ieVersion = Pattern.compile(
+ "compatible;"
+ + "\\s*"
+ + "\\w*" /* Browser name */
+ + "[\\s|/]"
+ +
+ "([A-Za-z]*"
+ /* Eat any letters before the major version */
+ +
+ "( [\\d]* )"
+ /* Major version number is every digit before first dot */
+ + "\\." /* The first dot */
+ +
+ "( [\\d]* )"
+ /* Minor version number is digits after first dot */
+ + "[^\\s]*)" /* Throw away remaining dots and digits */
+ , Pattern.COMMENTS);
+ private static Pattern safariVersion = Pattern.compile(
+ "safari/"
+ +
+ "(( [\\d]* )"
+ /* Major version number is every digit before first dot */
+ + "(?:"
+ + "\\." /* The first dot */
+ +
+ " [\\d]* )?)"
+ /* Minor version number is digits after first dot */
+ , Pattern.COMMENTS);
+ private static Pattern mozillaVersion = Pattern.compile(
+ "netscape/"
+ +
+ "(( [\\d]* )"
+ /* Major version number is every digit before the first dot */
+ + "\\." /* The first dot */
+ +
+ "( [\\d]* )"
+ /* Minor version number is every digit after the first dot */
+ + "[^\\s]*)" /* Throw away the remaining */
+ , Pattern.COMMENTS);
+ private static Pattern fallbackVersion = Pattern.compile(
+ "[\\w]+/"
+ +
+ "( [\\d]+ );"
+ /* Major version number is every digit before the first dot */
+ , Pattern.COMMENTS);
+
+
+ /* Accept-Language header variables */
+ private String acceptLanguage = null;
+ private SortedMap<Float,List<String>> languageRangesByQuality = null;
+ private String starLanguageRange = null;
+ // pametrizable filter of retained laguages
+ private List<String> languagesFilter = null;
+ private String preferredLanguage = null;
+
+ private static Pattern quality =
Pattern.compile("^q\\s*=\\s*(\\d(?:0(?:.\\d{0,3})?|1(?:.0{0,3}))?)$");
+
/**
* Retrieves the User-Agent header from the request (if any).
* @see #setUserAgent
@@ -82,14 +177,29 @@
if (request != null)
{
setUserAgent(request.getHeader("User-Agent"));
+ setAcceptLanguage(request.getHeader("Accept-Language"));
}
else
{
setUserAgent(null);
+ setAcceptLanguage(null);
}
}
/**
+ * Set log.
+ */
+ public void setLog(Log log)
+ {
+ if (log == null)
+ {
+ throw new NullPointerException("log should not be set to null");
+ }
+ this.LOG = log;
+ }
+
+
+ /**
* Sets the User-Agent string to be parsed for info. If null, the string
* will be empty and everything will return false or null. Otherwise,
* it will set the whole string to lower case before storing to simplify
@@ -107,6 +217,37 @@
}
}
+ public void setAcceptLanguage(String al)
+ {
+ if(al == null)
+ {
+ acceptLanguage = "";
+ }
+ else
+ {
+ acceptLanguage = al.toLowerCase();
+ }
+ }
+
+ public void setLanguagesFilter(String filter)
+ {
+ if(filter == null || filter.length() == 0)
+ {
+ languagesFilter = null;
+ }
+ else
+ {
+ languagesFilter = Arrays.asList(filter.split(","));
+ }
+ // clear preferred language cache
+ preferredLanguage = null;
+ }
+
+ public String getLanguagesFilter()
+ {
+ return languagesFilter.toString();
+ }
+
@Override
public String toString()
{
@@ -121,8 +262,14 @@
return test(key);
}
- public String getUserAgent() {
- return userAgent;
+ public String getUserAgent()
+ {
+ return userAgent;
+ }
+
+ public String getAcceptLanguage()
+ {
+ return acceptLanguage;
}
/* Versioning */
@@ -927,6 +1074,46 @@
return getDom0(); // good approximation
}
+ /* Languages */
+
+ public String getPreferredLanguage()
+ {
+ if(preferredLanguage != null) return preferredLanguage;
+
+ parseAcceptLanguage();
+ if(languageRangesByQuality.size() == 0)
+ {
+ preferredLanguage = starLanguageRange; // may be null
+ }
+ else
+ {
+ List<List<String>> lists = new
ArrayList<List<String>>(languageRangesByQuality.values());
+ Collections.reverse(lists);
+ for(List<String> lst : lists) // sorted by quality (treemap)
+ {
+ for(String l : lst)
+ {
+ preferredLanguage = filterLanguageTag(l);
+ if(preferredLanguage != null) break;
+ }
+ if(preferredLanguage != null) break;
+ }
+ }
+ // fallback
+ if(preferredLanguage == null)
+ {
+ preferredLanguage = filterLanguageTag(languagesFilter == null ?
getLocale().getDisplayName() : languagesFilter.get(0));
+ }
+ // preferredLanguage should now never be null
+ assert(preferredLanguage != null);
+ return preferredLanguage;
+ }
+
+ public Locale getPreferredLocale()
+ {
+ return ConversionUtils.toLocale(getPreferredLanguage());
+ }
+
/* Helpers */
private boolean test(String key)
@@ -944,21 +1131,7 @@
}
/* generic versionning */
- Matcher v = Pattern.compile(
- "/"
- /* Version starts with a slash */
- +
- "([A-Za-z]*"
- /* Eat any letters before the major version */
- +
- "( [\\d]* )"
- /* Major version number is every digit before the first
dot */
- + "\\." /* The first dot */
- +
- "( [\\d]* )"
- /* Minor version number is every digit after the first dot
*/
- + "[^\\s]*)" /* Throw away the remaining */
- , Pattern.COMMENTS).matcher(userAgent);
+ Matcher v = genericVersion.matcher(userAgent);
if (v.find())
{
@@ -969,7 +1142,7 @@
}
try
{
- majorVersion = Integer.parseInt(v.group(2));
+ majorVersion = Integer.valueOf(v.group(2));
String minor = v.group(3);
if (minor.startsWith("0"))
{
@@ -977,34 +1150,25 @@
}
else
{
- minorVersion = Integer.parseInt(minor);
+ minorVersion = Integer.valueOf(minor);
}
}
catch (NumberFormatException nfe)
- {}
+ {
+ LOG.error("BrowserTool: Could not parse browser version
for User-Agent: "+userAgent,nfe);
+ }
}
/* Firefox versionning */
if (test("firefox"))
{
- Matcher fx = Pattern.compile(
- "/"
- +
- "(( [\\d]* )"
- /* Major version number is every digit before the
first dot */
- + "\\." /* The first dot */
- +
- "( [\\d]* )"
- /* Minor version number is every digit after the first
dot */
- + "[^\\s]*)" /* Throw away the remaining */
- , Pattern.COMMENTS)
- .matcher(userAgent);
+ Matcher fx = firefoxVersion.matcher(userAgent);
if (fx.find())
{
version = fx.group(1);
try
{
- majorVersion = Integer.parseInt(fx.group(2));
+ majorVersion = Integer.valueOf(fx.group(2));
String minor = fx.group(3);
if (minor.startsWith("0"))
{
@@ -1012,41 +1176,27 @@
}
else
{
- minorVersion = Integer.parseInt(minor);
+ minorVersion = Integer.valueOf(minor);
}
}
catch (NumberFormatException nfe)
- {}
+ {
+ LOG.error("BrowserTool: Could not parse browser
version for User-Agent: "+userAgent,nfe);
+ }
}
}
/* IE versionning */
- if (test("compatible"))
+ else if (test("compatible"))
{
- Matcher ie = Pattern.compile(
- "compatible;"
- + "\\s*"
- + "\\w*" /* Browser name */
- + "[\\s|/]"
- +
- "([A-Za-z]*"
- /* Eat any letters before the major version */
- +
- "( [\\d]* )"
- /* Major version number is every digit before first
dot */
- + "\\." /* The first dot */
- +
- "( [\\d]* )"
- /* Minor version number is digits after first dot */
- + "[^\\s]*)" /* Throw away remaining dots and digits */
- , Pattern.COMMENTS)
- .matcher(userAgent);
+ Matcher ie = ieVersion.matcher(userAgent);
+
if (ie.find())
{
version = ie.group(1);
try
{
- majorVersion = Integer.parseInt(ie.group(2));
+ majorVersion = Integer.valueOf(ie.group(2));
String minor = ie.group(3);
if (minor.startsWith("0"))
{
@@ -1054,64 +1204,46 @@
}
else
{
- minorVersion = Integer.parseInt(minor);
+ minorVersion = Integer.valueOf(minor);
}
}
catch (NumberFormatException nfe)
- {}
+ {
+ LOG.error("BrowserTool: Could not parse browser
version for User-Agent: "+userAgent,nfe);
+ }
}
}
/* Safari versionning*/
- if (getSafari())
+ else if (getSafari())
{
- Matcher safari = Pattern.compile(
- "safari/"
- +
- "(( [\\d]* )"
- /* Major version number is every digit before first
dot */
- + "(?:"
- + "\\." /* The first dot */
- +
- " [\\d]* )?)"
- /* Minor version number is digits after first dot */
- , Pattern.COMMENTS)
- .matcher(userAgent);
+ Matcher safari = safariVersion.matcher(userAgent);
if (safari.find())
{
version = safari.group(1);
try
{
- int sv = Integer.parseInt(safari.group(2));
+ int sv = Integer.valueOf(safari.group(2));
majorVersion = sv / 100;
minorVersion = sv % 100;
}
catch (NumberFormatException nfe)
- {}
+ {
+ LOG.error("BrowserTool: Could not parse browser
version for User-Agent: "+userAgent,nfe);
+ }
}
}
/* Gecko-powered Netscape (i.e. Mozilla) versions */
- if (getGecko() && getNetscape() && test("netscape"))
+ else if (getGecko() && getNetscape() && test("netscape"))
{
- Matcher netscape = Pattern.compile(
- "netscape/"
- +
- "(( [\\d]* )"
- /* Major version number is every digit before the
first dot */
- + "\\." /* The first dot */
- +
- "( [\\d]* )"
- /* Minor version number is every digit after the first
dot */
- + "[^\\s]*)" /* Throw away the remaining */
- , Pattern.COMMENTS)
- .matcher(userAgent);
+ Matcher netscape = mozillaVersion.matcher(userAgent);
if (netscape.find())
{
version = netscape.group(1);
try
{
- majorVersion = Integer.parseInt(netscape.group(2));
+ majorVersion = Integer.valueOf(netscape.group(2));
String minor = netscape.group(3);
if (minor.startsWith("0"))
{
@@ -1119,34 +1251,32 @@
}
else
{
- minorVersion = Integer.parseInt(minor);
+ minorVersion = Integer.valueOf(minor);
}
}
catch (NumberFormatException nfe)
- {}
+ {
+ LOG.error("BrowserTool: Could not parse browser
version for User-Agent: "+userAgent,nfe);
+ }
}
}
/* last try if version not found */
if (version == null)
{
- Matcher mv = Pattern.compile(
- "[\\w]+/"
- +
- "( [\\d]+ );"
- /* Major version number is every digit before the
first dot */
- , Pattern.COMMENTS)
- .matcher(userAgent);
+ Matcher mv = fallbackVersion.matcher(userAgent);
if (mv.find())
{
version = mv.group(1);
try
{
- majorVersion = Integer.parseInt(version);
+ majorVersion = Integer.valueOf(version);
minorVersion = 0;
}
catch (NumberFormatException nfe)
- {}
+ {
+ LOG.error("BrowserTool: Could not parse browser
version for User-Agent: "+userAgent,nfe);
+ }
}
}
@@ -1161,7 +1291,7 @@
geckoVersion = g.group(1);
try
{
- geckoMajorVersion = Integer.parseInt(g.group(2));
+ geckoMajorVersion = Integer.valueOf(g.group(2));
String minor = g.group(3);
if (minor.startsWith("0"))
{
@@ -1169,19 +1299,88 @@
}
else
{
- geckoMinorVersion = Integer.parseInt(minor);
+ geckoMinorVersion = Integer.valueOf(minor);
}
}
catch (NumberFormatException nfe)
- {}
+ {
+ LOG.error("BrowserTool: Could not parse browser
version for User-Agent: "+userAgent,nfe);
+ }
}
}
}
- catch (PatternSyntaxException nfe)
+ catch (PatternSyntaxException pse)
{
- // where should I log ?!
+ LOG.error("BrowserTool: Could not parse browser version for
User-Agent: "+userAgent,pse);
}
}
+ private void parseAcceptLanguage()
+ {
+ if(languageRangesByQuality != null)
+ {
+ // already done
+ return;
+ }
+
+ languageRangesByQuality = new TreeMap<Float,List<String>>();
+ StringTokenizer languageTokenizer = new
StringTokenizer(acceptLanguage, ",");
+ while (languageTokenizer.hasMoreTokens())
+ {
+ String language = languageTokenizer.nextToken().trim();
+ int qValueIndex = language.indexOf(';');
+ if(qValueIndex == -1)
+ {
+ language = language.replace('-','_');
+ List<String> l = languageRangesByQuality.get(1.0f);
+ if(l == null)
+ {
+ l = new ArrayList<String>();
+ languageRangesByQuality.put(1.0f,l);
+ }
+ l.add(language);
+ }
+ else
+ {
+ String code =
language.substring(0,qValueIndex).trim().replace('-','_');
+ String qval = language.substring(qValueIndex + 1).trim();
+ if("*".equals(qval))
+ {
+ starLanguageRange = code;
+ }
+ else
+ {
+ Matcher m = quality.matcher(qval);
+ if(m.matches())
+ {
+ Float q = Float.valueOf(m.group(1));
+ List<String> al = languageRangesByQuality.get(q);
+ if(al == null)
+ {
+ al = new ArrayList<String>();
+ languageRangesByQuality.put(q,al);
+ }
+ al.add(code);
+ }
+ else
+ {
+ LOG.error("BrowserTool: could not parse language
quality value: "+language);
+ }
+ }
+ }
+ }
+ }
+ private String filterLanguageTag(String languageTag)
+ {
+ languageTag = languageTag.replace('-','_');
+ if(languagesFilter == null) return languageTag;
+ if(languagesFilter.contains(languageTag)) return languageTag;
+ if(languageTag.contains("_"))
+ {
+ String[] parts = languageTag.split("_");
+ if(languagesFilter.contains(parts[0])) return parts[0];
+ }
+ return null;
+ }
}
Modified:
velocity/tools/trunk/src/test/java/org/apache/velocity/tools/test/blackbox/ViewToolsTests.java
URL:
http://svn.apache.org/viewvc/velocity/tools/trunk/src/test/java/org/apache/velocity/tools/test/blackbox/ViewToolsTests.java?rev=803412&r1=803411&r2=803412&view=diff
==============================================================================
---
velocity/tools/trunk/src/test/java/org/apache/velocity/tools/test/blackbox/ViewToolsTests.java
(original)
+++
velocity/tools/trunk/src/test/java/org/apache/velocity/tools/test/blackbox/ViewToolsTests.java
Wed Aug 12 08:26:50 2009
@@ -163,12 +163,19 @@
/******* Tests **********/
public @Test void testBrowserSnifferTool() throws Exception {
+ /* check we are identified as a Java (HttpUnit) client */
WebConversation conv = new WebConversation();
WebRequest req = new GetMethodWebRequest(ROOT_URL+"browser.vm");
WebResponse resp = conv.getResponse(req);
-
- /* check we are identified as a Java (HttpUnit) client */
checkText(resp,"Java","true");
+
+ /* check language */
+ req.setHeaderField("Accept-Language","en");
+ resp = conv.getResponse(req);
+ checkText(resp,"prefLang","en");
+ req.setHeaderField("Accept-Language","en-US,en;q=0.8");
+ resp = conv.getResponse(req);
+ checkText(resp,"preferredLanguage","en");
}
public @Test void testContextTool() throws Exception {