001/*
002 * File   : $Source$
003 * Date   : $Date$
004 * Version: $Revision$
005 *
006 * This library is part of OpenCms -
007 * the Open Source Content Management System
008 *
009 * Copyright (C) 2002 - 2009 Alkacon Software (http://www.alkacon.com)
010 *
011 * This library is free software; you can redistribute it and/or
012 * modify it under the terms of the GNU Lesser General Public
013 * License as published by the Free Software Foundation; either
014 * version 2.1 of the License, or (at your option) any later version.
015 *
016 * This library is distributed in the hope that it will be useful,
017 * but WITHOUT ANY WARRANTY; without even the implied warranty of
018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019 * Lesser General Public License for more details.
020 *
021 * For further information about Alkacon Software, please see the
022 * company website: http://www.alkacon.com
023 *
024 * For further information about OpenCms, please see the
025 * project website: http://www.opencms.org
026 *
027 * You should have received a copy of the GNU Lesser General Public
028 * License along with this library; if not, write to the Free Software
029 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
030 */
031
032package org.opencms.jsp;
033
034import org.opencms.i18n.CmsResourceBundleLoader;
035
036import java.util.Locale;
037import java.util.MissingResourceException;
038import java.util.ResourceBundle;
039
040import javax.servlet.ServletResponse;
041import javax.servlet.jsp.JspException;
042import javax.servlet.jsp.PageContext;
043import javax.servlet.jsp.jstl.fmt.LocalizationContext;
044
045import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager;
046import org.apache.taglibs.standard.tag.common.fmt.BundleSupport;
047import org.apache.taglibs.standard.tag.common.fmt.SetLocaleSupport;
048import org.apache.taglibs.standard.tag.el.fmt.BundleTag;
049
050/**
051 * Provides tag access to OpenCms resource bundles.<p>
052 *
053 * This replaces the <code>&lt;fmt:bundle basename=""&gt;</code> tag which is not capable of using OpenCms resource bundles.<p>
054 *
055 * You can use <code>&lt;fmt:message key=""&gt;</code> tags inside the <code>&lt;cms:bundle basename=""&gt;</code> tag as usual.
056 *
057 * @since 8.5.2
058 */
059public class CmsJspTagBundle extends BundleTag {
060
061    /** Serial version UID required for safe serialization. */
062    private static final long serialVersionUID = 7592250223728101278L;
063
064    /** The basename attribute value. */
065    private String m_basename;
066
067    /** The localization to use. */
068    private LocalizationContext m_locCtxt;
069
070    /** The prefix attribute value. */
071    private String m_prefix;
072
073    /**
074     * Empty constructor.<p>
075     */
076    public CmsJspTagBundle() {
077
078        super();
079        init();
080    }
081
082    /**
083     * Returns the initialized localization context.<p>
084     * @param pc the current page context
085     * @param basename the bas name of the bundle
086     *
087     * @return the initialized localization context
088     */
089    public static LocalizationContext getLocalizationContext(PageContext pc, String basename) {
090
091        LocalizationContext locCtxt = null;
092        ResourceBundle bundle = null;
093
094        if ((basename == null) || basename.equals("")) {
095            return new LocalizationContext();
096        }
097
098        // Try preferred locales
099        Locale pref = getLocale(pc, javax.servlet.jsp.jstl.core.Config.FMT_LOCALE);
100        if (pref != null) {
101            // Preferred locale is application-based
102            bundle = findMatch(basename, pref);
103            if (bundle != null) {
104                locCtxt = new LocalizationContext(bundle, pref);
105            }
106        }
107
108        if (locCtxt == null) {
109            // No match found with preferred locales, try using fallback locale
110            locCtxt = BundleSupport.getLocalizationContext(pc, basename);
111        } else {
112            // set response locale
113            if (locCtxt.getLocale() != null) {
114                setResponseLocale(pc, locCtxt.getLocale());
115            }
116        }
117
118        return locCtxt;
119    }
120
121    /**
122     * Returns the locale specified by the named scoped attribute or context
123     * configuration parameter.
124     *
125     * <p> The named scoped attribute is searched in the page, request,
126     * session (if valid), and application scope(s) (in this order). If no such
127     * attribute exists in any of the scopes, the locale is taken from the
128     * named context configuration parameter.
129     *
130     * @param pageContext the page in which to search for the named scoped
131     * attribute or context configuration parameter
132     * @param name the name of the scoped attribute or context configuration
133     * parameter
134     *
135     * @return the locale specified by the named scoped attribute or context
136     * configuration parameter, or <tt>null</tt> if no scoped attribute or
137     * configuration parameter with the given name exists
138     */
139    static Locale getLocale(PageContext pageContext, String name) {
140
141        Locale loc = null;
142
143        Object obj = javax.servlet.jsp.jstl.core.Config.find(pageContext, name);
144        if (obj != null) {
145            if (obj instanceof Locale) {
146                loc = (Locale)obj;
147            } else {
148                loc = SetLocaleSupport.parseLocale((String)obj);
149            }
150        }
151
152        return loc;
153    }
154
155    /**
156     * Stores the given locale in the response object of the given page
157     * context, and stores the locale's associated charset in the
158     * javax.servlet.jsp.jstl.fmt.request.charset session attribute, which
159     * may be used by the <requestEncoding> action in a page invoked by a
160     * form included in the response to set the request charset to the same as
161     * the response charset (this makes it possible for the container to
162     * decode the form parameter values properly, since browsers typically
163     * encode form field values using the response's charset).
164     *
165     * @param pc the page context whose response object is assigned
166     * the given locale
167     * @param locale the response locale
168     */
169    static void setResponseLocale(PageContext pc, Locale locale) {
170
171        // set response locale
172        ServletResponse response = pc.getResponse();
173        response.setLocale(locale);
174
175        // get response character encoding and store it in session attribute
176        if (pc.getSession() != null) {
177            try {
178                pc.setAttribute(
179                    "javax.servlet.jsp.jstl.fmt.request.charset",
180                    response.getCharacterEncoding(),
181                    PageContext.SESSION_SCOPE);
182            } catch (IllegalStateException ex) {
183                // invalidated session ignored
184            }
185        }
186    }
187
188    /**
189     * Gets the resource bundle with the given base name and preferred locale.
190     *
191     * @param basename the resource bundle base name
192     * @param pref the preferred locale
193     * @return the resource bundle.
194     */
195    private static ResourceBundle findMatch(String basename, Locale pref) {
196
197        ResourceBundle match = null;
198        try {
199            ResourceBundle bundle = CmsResourceBundleLoader.getBundle(basename, pref);
200            match = bundle;
201        } catch (MissingResourceException mre) {
202            // ignored
203        }
204        return match;
205    }
206
207    /**
208     * Internal action method.<p>
209     *
210     * @return EVAL_BODY_BUFFERED
211     * @see javax.servlet.jsp.tagext.Tag#doStartTag()
212     */
213    @Override
214    public int doStartTag() throws JspException {
215
216        evaluateExpressions();
217        m_locCtxt = getLocalizationContext(pageContext, basename);
218        return EVAL_BODY_BUFFERED;
219    }
220
221    /**
222     * Returns the localization context to use.<p>
223     *
224     * @see org.apache.taglibs.standard.tag.common.fmt.BundleSupport#getLocalizationContext()
225     */
226    @Override
227    public LocalizationContext getLocalizationContext() {
228
229        return m_locCtxt;
230    }
231
232    /**
233     * @see org.apache.taglibs.standard.tag.el.fmt.BundleTag#release()
234     */
235    @Override
236    public void release() {
237
238        super.release();
239        init();
240    }
241
242    /**
243     * Sets the basename attribute value.<p>
244     *
245     * @param bn the basename attribute value
246     *
247     * @see org.apache.taglibs.standard.tag.el.fmt.BundleTag#setBasename(java.lang.String)
248     */
249    @Override
250    public void setBasename(String bn) {
251
252        m_basename = bn;
253    }
254
255    /**
256     * Sets the prefix attribute value.<p>
257     *
258     * @param pf the prefix attribute value
259     *
260     * @see org.apache.taglibs.standard.tag.el.fmt.BundleTag#setPrefix(java.lang.String)
261     */
262    @Override
263    public void setPrefix(String pf) {
264
265        m_prefix = pf;
266    }
267
268    /**
269     * Evaluates expressions as neccessary.
270     *
271     * This is a copy of the private method from the {@link BundleTag}.
272     *
273     * @throws JspException same as for the default {@link BundleTag}.
274     */
275    private void evaluateExpressions() throws JspException {
276
277        // 'basename' attribute (mandatory)
278        basename = (String)ExpressionEvaluatorManager.evaluate("basename", m_basename, String.class, this, pageContext);
279
280        // 'prefix' attribute (optional)
281        if (m_prefix != null) {
282            prefix = (String)ExpressionEvaluatorManager.evaluate("prefix", m_prefix, String.class, this, pageContext);
283        }
284    }
285
286    /**
287     * Sets the initial state.
288     */
289    private void init() {
290
291        m_basename = null;
292        m_locCtxt = null;
293        m_prefix = null;
294    }
295}