001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry.services.impl;
016    
017    import org.apache.hivemind.service.ThreadLocale;
018    import org.apache.tapestry.TapestryConstants;
019    import org.apache.tapestry.TapestryUtils;
020    import org.apache.tapestry.services.CookieSource;
021    import org.apache.tapestry.services.RequestLocaleManager;
022    import org.apache.tapestry.web.WebRequest;
023    
024    import java.util.*;
025    
026    /**
027     * Service tapestry.request.RequestLocaleManager. Identifies the Locale provided by the client
028     * (either in a Tapestry-specific cookie, or interpolated from the HTTP header.
029     *
030     * @author Howard Lewis Ship
031     * @since 4.0
032     */
033    public class RequestLocaleManagerImpl implements RequestLocaleManager
034    {
035        private WebRequest _request;
036    
037        /**
038         * Extracted at start of request, and used at end of request to see if locale has changed.
039         * Because of this thread-specific state, the service must use the threaded service lifecycle
040         * model.
041         */
042    
043        private Locale _requestLocale;
044    
045        private CookieSource _cookieSource;
046    
047        private ThreadLocale _threadLocale;
048    
049        /**
050         * Set from symbol org.apache.tapestry.accepted-locales, a comma-seperated list of locale names.
051         * The first name is the default for requests that can't be matched against the other locale
052         * names. May also be blank, in which case, whatever locale was provided in the request is
053         * accepted (which is Tapestry 3.0 behavior).
054         */
055    
056        private String _acceptedLocales;
057    
058        private Locale _defaultLocale;
059    
060        /**
061         * Set of locale names. Incoming requests will be matched to one of these locales.
062         */
063    
064        private Set _acceptedLocaleNamesSet = new HashSet();
065    
066        /**
067         * Cache of Locales, keyed on locale name.
068         */
069    
070        private Map _localeCache = new HashMap();
071    
072        /**
073         * Reference to last persisted locale, if any.  Used to prevent
074         * duplicate cookie writes of the same locale.
075         */
076        private Locale _lastPersisted;
077    
078        public void initializeService()
079        {
080            String[] names = TapestryUtils.split(_acceptedLocales);
081    
082            if (names.length == 0)
083                return;
084    
085            _defaultLocale = getLocale(names[0]);
086    
087            _acceptedLocaleNamesSet.addAll(Arrays.asList(names));
088        }
089    
090        public Locale extractLocaleForCurrentRequest()
091        {
092            String localeName = _cookieSource.readCookieValue(TapestryConstants.LOCALE_COOKIE_NAME);
093    
094            String requestedLocale = (localeName != null) ? localeName : _request.getLocale().toString();
095    
096            _requestLocale = filterRequestedLocale(requestedLocale);
097    
098            _threadLocale.setLocale(_requestLocale);
099    
100            return _requestLocale;
101        }
102    
103        /**
104         * Converts the request locale name into a Locale instance; applies filters (based on
105         * acceptedLocales) if enabled.
106         */
107    
108        Locale filterRequestedLocale(String localeName)
109        {
110            String requestLocaleName = localeName;
111            if (_acceptedLocaleNamesSet.isEmpty())
112                return getLocale(requestLocaleName);
113    
114            while (requestLocaleName.length() > 0)
115            {
116                if (_acceptedLocaleNamesSet.contains(requestLocaleName))
117                    return getLocale(requestLocaleName);
118    
119                requestLocaleName = stripTerm(requestLocaleName);
120            }
121    
122            // now try "best match"
123    
124            for (Iterator it = _acceptedLocaleNamesSet.iterator(); it.hasNext();)
125            {
126                String locale = (String) it.next();
127    
128                if (locale.startsWith(localeName))
129                    return getLocale(locale);
130            }
131    
132            return _defaultLocale;
133        }
134    
135        private String stripTerm(String localeName)
136        {
137            int scorex = localeName.lastIndexOf('_');
138    
139            return scorex < 0 ? "" : localeName.substring(0, scorex);
140        }
141    
142        public void persistLocale()
143        {
144            Locale locale = _threadLocale.getLocale();
145    
146            if (locale.equals(_requestLocale)
147                || _lastPersisted != null && locale.equals(_lastPersisted))
148                return;
149    
150            _cookieSource.writeCookieValue(TapestryConstants.LOCALE_COOKIE_NAME, locale.toString());
151            _lastPersisted = locale;
152        }
153    
154        Locale getLocale(String name)
155        {
156            Locale result = (Locale) _localeCache.get(name);
157    
158            if (result == null)
159            {
160                result = constructLocale(name);
161                _localeCache.put(name, result);
162            }
163    
164            return result;
165        }
166    
167        private Locale constructLocale(String name)
168        {
169            String[] terms = TapestryUtils.split(name, '_');
170    
171            switch (terms.length)
172            {
173                case 1:
174                    return new Locale(terms[0], "");
175    
176                case 2:
177                    return new Locale(terms[0], terms[1]);
178    
179                case 3:
180    
181                    return new Locale(terms[0], terms[1], terms[2]);
182    
183                default:
184    
185                    throw new IllegalArgumentException();
186            }
187        }
188    
189        public void setCookieSource(CookieSource source)
190        {
191            _cookieSource = source;
192        }
193    
194        public void setRequest(WebRequest request)
195        {
196            _request = request;
197        }
198    
199        public void setThreadLocale(ThreadLocale threadLocale)
200        {
201            _threadLocale = threadLocale;
202        }
203    
204        public void setAcceptedLocales(String acceptedLocales)
205        {
206            _acceptedLocales = acceptedLocales;
207        }
208    }