KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > matching > LocaleMatcher


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.matching;
17
18 import org.apache.avalon.framework.activity.Disposable;
19 import org.apache.avalon.framework.configuration.Configurable;
20 import org.apache.avalon.framework.configuration.Configuration;
21 import org.apache.avalon.framework.logger.AbstractLogEnabled;
22 import org.apache.avalon.framework.parameters.Parameters;
23 import org.apache.avalon.framework.service.ServiceException;
24 import org.apache.avalon.framework.service.ServiceManager;
25 import org.apache.avalon.framework.service.Serviceable;
26 import org.apache.avalon.framework.thread.ThreadSafe;
27
28 import org.apache.cocoon.i18n.I18nUtils;
29 import org.apache.cocoon.sitemap.PatternException;
30
31 import org.apache.commons.lang.StringUtils;
32 import org.apache.excalibur.source.Source;
33 import org.apache.excalibur.source.SourceResolver;
34
35 import java.io.IOException JavaDoc;
36 import java.util.HashMap JavaDoc;
37 import java.util.Locale JavaDoc;
38 import java.util.Map JavaDoc;
39
40 /**
41  * A matcher that locates and identifies to the pipeline a source document to
42  * be used as the content for an i18n site, based upon a locale provided in a
43  * range of ways.
44  *
45  * <h1>Configuration</h1>
46  * <p>A sample configuration (given in the &lt;map:matchers&gt; section of the
47  * sitemap) is given below. This configuration shows default values.
48  * </p>
49  * <pre>
50  * &lt;map:matcher name="i18n" SRC="org.apache.cocoon.matching.LocaleMatcher"&gt;
51  * &lt;locale-attribute&gt;locale&lt;/locale-attribute&gt;
52  * &lt;negotiate&gt;false&lt;/negotiate&gt;
53  * &lt;use-locale&gt;true&lt;/use-locale&gt;
54  * &lt;use-locales&gt;false&lt;/use-locales&gt;
55  * &lt;use-blank-locale&gt;true&lt;/use-blank-locale&gt;
56  * &lt;default-locale language="en" country="US"/&gt;
57  * &lt;store-in-request&gt;false&lt;store-in-request&gt;
58  * &lt;create-session&gt;false&lt;create-session&gt;
59  * &lt;store-in-session&gt;false&lt;store-in-session&gt;
60  * &lt;store-in-cookie&gt;false&lt;store-in-cookie&gt;
61  * &lt;/map:matcher&gt;
62  * </pre>
63  *
64  * <p>Above configuration parameters mean:
65  * <ul>
66  * <li><b>locale-attribute</b> specifies the name of the request
67  * parameter / session attribute / cookie that is to be used as a locale
68  * (defaults to <code>locale</code>)</li>
69  * <li><b>negotiate</b> specifies whether matcher should check that
70  * resource exists. If set to true, matcher will look for the locale
71  * till matching resource is found. If no resource found even with
72  * default or blank locale, matcher will not match.</li>
73  * <li><b>use-locale</b> specifies whether the primary locale provided
74  * by the user agent (or server default, is no locale passed by the agent)
75  * is to be used</li>
76  * <li><b>use-locales</b> specifies whether each locale provided by the
77  * user agent should be tested in turn (makes sense only when
78  * <code>negotiate</code> is set to <code>true</code>)</li>
79  * <li><b>default-locale</b> specifies the default locale to be used when
80  * none matches any of the previous ones.</li>
81  * <li><b>use-blank-locale</b> specifies whether a file should be looked
82  * for without a locale in its filename or filepath (e.g. after looking
83  * for index.en.html, try index.html) if none matches any of the previous
84  * locales.</li>
85  * <li><b>store-in-request</b> specifies whether found locale should be
86  * stored as request attribute.</li>
87  * <li><b>create-session</b> specifies whether session should be created
88  * when storing found locale as session attribute.</li>
89  * <li><b>store-in-session</b> specifies whether found locale should be
90  * stored as session attribute.</li>
91  * <li><b>store-in-cookie</b> specifies whether found locale should be
92  * stored as cookie.</li>
93  * </ul>
94  * </p>
95  *
96  * <h1>Usage</h1>
97  * <p>This matcher will be used in a pipeline like so:</p>
98  * <pre>
99  * &lt;map:match pattern="*.html"&gt;
100  * &lt;map:match type="i18n" pattern="xml/{1}.*.xml"&gt;
101  * &lt;map:generate SRC="{source}"/&gt;
102  * ...
103  * &lt;/map:match&gt;
104  * &lt;/map:match&gt;
105  * </pre>
106  * <p><code>*</code> in the pattern identifies the place where locale should
107  * be inserted. In case of a blank locale, if character before and after
108  * <code>*</code> is the same (like in example above), duplicate will
109  * be removed (<code>xml/{1}.*.xml</code> becomes <code>xml/{1}.xml</code>).</p>
110  *
111  * <h1>Locale Identification</h1>
112  * <p>Locales will be tested in following order:</p>
113  * <ul>
114  * <li>Locale provided as a request parameter</li>
115  * <li>Locale provided as a session attribute</li>
116  * <li>Locale provided as a cookie</li>
117  * <li>Locale provided using a sitemap parameter<br>
118  * (&lt;map:parameter name="locale" value="{1}"/&gt; style parameter within
119  * the &lt;map:match&gt; node)</li>
120  * <li>Locale provided by the user agent, or server default,
121  * if <code>use-locale</code> is set to <code>true</code></li>
122  * <li>Locales provided by the user agent, if <code>use-locales</code>
123  * is set to <code>true</code>.</li>
124  * <li>The default locale, if specified in the matcher's configuration</li>
125  * <li>Resources with no defined locale (blank locale)</li>
126  * </ul>
127  * <p>If <code>negotiate</code> mode is set to <code>true</code>, a source will
128  * be looked up using each locale. Where the full locale (language, country,
129  * variant) doesn't match, it will fall back first to language and country,
130  * and then just language, before moving on to the next locale.</p>
131  * <p>If <code>negotiate</code> mode is set to <code>false</code> (default),
132  * first found locale will be returned.</p>
133  *
134  * <h1>Sitemap Variables</h1>
135  * <p>Once a matching locale has been found, the following sitemap variables
136  * will be available to sitemap elements contained within the matcher:</p>
137  * <ul>
138  * <li>{source}: The URI of the source that matched</li>
139  * <li>{locale}: The locale that matched that resource</li>
140  * <li>{matched-locale}: The part of the locale that matched the resource</li>
141  * <li>{language}: The language of the matching resource</li>
142  * <li>{country}: The country of the matching resource</li>
143  * <li>{variant}: The variant of the matching resource</li>
144  * </ul>
145  *
146  * @since 2.1.6
147  * @author <a HREF="mailto:uv@upaya.co.uk">Upayavira</a>
148  * @author <a HREF="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
149  * @version CVS $Id: LocaleMatcher.java 264790 2005-08-30 14:45:51Z vgritsenko $
150  */

151 public class LocaleMatcher extends AbstractLogEnabled
152                            implements Matcher, ThreadSafe, Serviceable, Configurable, Disposable {
153
154     private static final String JavaDoc DEFAULT_LOCALE_ATTRIBUTE = "locale";
155     private static final String JavaDoc DEFAULT_DEFAULT_LANG = "en";
156     private static final String JavaDoc DEFAULT_DEFAULT_COUNTRY = "US";
157     private static final String JavaDoc DEFAULT_DEFAULT_VARIANT = "";
158
159     private ServiceManager manager;
160     private SourceResolver resolver;
161
162     /**
163      * Name of the locale request parameter, session attribute, cookie.
164      */

165     private String JavaDoc localeAttribute;
166
167     /**
168      * Whether to query locale provided by the user agent or not.
169      */

170     private boolean useLocale;
171
172     private boolean useLocales;
173     private Locale JavaDoc defaultLocale;
174     private boolean useBlankLocale;
175     private boolean testResourceExists;
176
177     /**
178      * Store the locale in request. Default is not to do this.
179      */

180     private boolean storeInRequest;
181
182     /**
183      * Store the locale in session, if available. Default is not to do this.
184      */

185     private boolean storeInSession;
186
187     /**
188      * Should we create a session if needed. Default is not to do this.
189      */

190     private boolean createSession;
191
192     /**
193      * Should we add a cookie with the locale. Default is not to do this.
194      */

195     private boolean storeInCookie;
196
197
198     public void service(ServiceManager manager) throws ServiceException {
199         this.manager = manager;
200         this.resolver = (SourceResolver)this.manager.lookup(SourceResolver.ROLE);
201     }
202
203     public void configure(Configuration config) {
204         this.storeInRequest = config.getChild("store-in-request").getValueAsBoolean(false);
205         this.createSession = config.getChild("create-session").getValueAsBoolean(false);
206         this.storeInSession = config.getChild("store-in-session").getValueAsBoolean(false);
207         this.storeInCookie = config.getChild("store-in-cookie").getValueAsBoolean(false);
208         if (getLogger().isDebugEnabled()) {
209             getLogger().debug((this.storeInRequest ? "will" : "won't") + " set values in request");
210             getLogger().debug((this.createSession ? "will" : "won't") + " create session");
211             getLogger().debug((this.storeInSession ? "will" : "won't") + " set values in session");
212             getLogger().debug((this.storeInCookie ? "will" : "won't") + " set values in cookies");
213         }
214
215         this.localeAttribute = config.getChild("locale-attribute").getValue(DEFAULT_LOCALE_ATTRIBUTE);
216         this.testResourceExists = config.getChild("negotiate").getValueAsBoolean(false);
217
218         this.useLocale = config.getChild("use-locale").getValueAsBoolean(true);
219         this.useLocales = config.getChild("use-locales").getValueAsBoolean(false);
220         this.useBlankLocale = config.getChild("use-blank-locale").getValueAsBoolean(true);
221
222         Configuration child = config.getChild("default-locale", false);
223         if (child != null) {
224             this.defaultLocale = new Locale JavaDoc(child.getAttribute("language", DEFAULT_DEFAULT_LANG),
225                                             child.getAttribute("country", DEFAULT_DEFAULT_COUNTRY),
226                                             child.getAttribute("variant", DEFAULT_DEFAULT_VARIANT));
227         }
228
229         if (getLogger().isDebugEnabled()) {
230             getLogger().debug("Locale attribute name is " + this.localeAttribute);
231             getLogger().debug((this.testResourceExists ? "will" : "won't") + " negotiate locale");
232             getLogger().debug((this.useLocale ? "will" : "won't") + " use request locale");
233             getLogger().debug((this.useLocales ? "will" : "won't") + " use request locales");
234             getLogger().debug((this.useBlankLocale ? "will" : "won't") + " blank locales");
235             getLogger().debug("default locale " + this.defaultLocale);
236         }
237     }
238
239     public void dispose() {
240         this.manager.release(this.resolver);
241         this.resolver = null;
242         this.manager = null;
243     }
244
245
246     public Map JavaDoc match(final String JavaDoc pattern, Map JavaDoc objectModel, Parameters parameters)
247     throws PatternException {
248         final Map JavaDoc map = new HashMap JavaDoc();
249
250         I18nUtils.LocaleValidator validator = new I18nUtils.LocaleValidator() {
251             public boolean test(String JavaDoc name, Locale JavaDoc locale) {
252                 if (getLogger().isDebugEnabled()) {
253                     getLogger().debug("Testing " + name + " locale: '" + locale + "'");
254                 }
255                 return isValidResource(pattern, locale, map);
256             }
257         };
258
259         Locale JavaDoc locale = I18nUtils.findLocale(objectModel,
260                                              localeAttribute,
261                                              parameters,
262                                              defaultLocale,
263                                              useLocale,
264                                              useLocales,
265                                              useBlankLocale,
266                                              validator);
267
268         if (locale == null) {
269             if (getLogger().isDebugEnabled()) {
270                 getLogger().debug("No locale found for resource: " + pattern);
271             }
272             return null;
273         }
274
275         String JavaDoc localeStr = locale.toString();
276         if (getLogger().isDebugEnabled()) {
277             getLogger().debug("Locale " + localeStr + " found for resource: " + pattern);
278         }
279
280         I18nUtils.storeLocale(objectModel,
281                               localeAttribute,
282                               localeStr,
283                               storeInRequest,
284                               storeInSession,
285                               storeInCookie,
286                               createSession);
287
288         return map;
289     }
290
291     private boolean isValidResource(String JavaDoc pattern, Locale JavaDoc locale, Map JavaDoc map) {
292         Locale JavaDoc testLocale;
293
294         // Test "language, country, variant" locale
295
if (locale.getVariant().length() > 0) {
296             if (isValidResource(pattern, locale, locale, map)) {
297                 return true;
298             }
299         }
300
301         // Test "language, country" locale
302
if (locale.getCountry().length() > 0) {
303             testLocale = new Locale JavaDoc(locale.getLanguage(), locale.getCountry());
304             if (isValidResource(pattern, locale, testLocale, map)) {
305                 return true;
306             }
307         }
308
309         // Test "language" locale (or empty - if language is "")
310
testLocale = new Locale JavaDoc(locale.getLanguage(), ""); // Use JDK1.3 constructor
311
if (isValidResource(pattern, locale, testLocale, map)) {
312             return true;
313         }
314
315         return false;
316     }
317
318     private boolean isValidResource(String JavaDoc pattern, Locale JavaDoc locale, Locale JavaDoc testLocale, Map JavaDoc map) {
319         String JavaDoc url;
320
321         String JavaDoc testLocaleStr = testLocale.toString();
322         if ("".equals(testLocaleStr)) {
323             // If same character found before and after the '*', leave only one.
324
int starPos = pattern.indexOf("*");
325             if (starPos < pattern.length() - 1 && starPos > 1 &&
326                     pattern.charAt(starPos - 1) == pattern.charAt(starPos + 1)) {
327                 url = pattern.substring(0, starPos - 1) + pattern.substring(starPos + 1);
328             } else {
329                 url = StringUtils.replace(pattern, "*", "");
330             }
331         } else {
332             url = StringUtils.replace(pattern, "*", testLocaleStr);
333         }
334
335         boolean result = true;
336         if (testResourceExists) {
337             Source source = null;
338             try {
339                 source = resolver.resolveURI(url);
340                 result = source.exists();
341             } catch (IOException JavaDoc e) {
342                 result = false;
343             } finally {
344                 if (source != null) {
345                     resolver.release(source);
346                 }
347             }
348         }
349
350         if (result) {
351             map.put("source", url);
352             map.put("matched-locale", testLocaleStr);
353             if (locale != null) {
354                 map.put("locale", locale.toString());
355                 map.put("language", locale.getLanguage());
356                 map.put("country", locale.getCountry());
357                 map.put("variant", locale.getVariant());
358             }
359         }
360
361         return result;
362     }
363 }
364
Popular Tags