001 /*
002 Licensed to the Apache Software Foundation (ASF) under one
003 or more contributor license agreements. See the NOTICE file
004 distributed with this work for additional information
005 regarding copyright ownership. The ASF licenses this file
006 to you under the Apache License, Version 2.0 (the
007 "License"); you may not use this file except in compliance
008 with the License. You may obtain a copy of the License at
009
010 http://www.apache.org/licenses/LICENSE-2.0
011
012 Unless required by applicable law or agreed to in writing,
013 software distributed under the License is distributed on an
014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 KIND, either express or implied. See the License for the
016 specific language governing permissions and limitations
017 under the License.
018 */
019 package org.apache.wiki.preferences;
020
021 import java.text.DateFormat;
022 import java.text.ParseException;
023 import java.text.SimpleDateFormat;
024 import java.util.*;
025
026 import javax.servlet.http.HttpServletRequest;
027 import javax.servlet.http.HttpSession;
028 import javax.servlet.jsp.PageContext;
029
030 import org.apache.commons.lang.StringUtils;
031 import org.apache.log4j.Logger;
032 import org.apache.wiki.InternalWikiException;
033 import org.apache.wiki.WikiContext;
034 import org.apache.wiki.i18n.InternationalizationManager;
035 import org.apache.wiki.util.HttpUtil;
036 import org.apache.wiki.util.PropertyReader;
037 import org.apache.wiki.util.TextUtil;
038 import org.json.JSONObject;
039
040 /**
041 * Represents an object which is used to store user preferences.
042 *
043 */
044 public class Preferences
045 extends HashMap<String,String>
046 {
047 private static final long serialVersionUID = 1L;
048
049 /**
050 * The name under which a Preferences object is stored in the HttpSession.
051 * Its value is {@value}.
052 */
053 public static final String SESSIONPREFS = "prefs";
054
055 private static Logger log = Logger.getLogger( Preferences.class );
056
057 /**
058 * This is an utility method which is called to make sure that the
059 * JSP pages do have proper access to any user preferences. It should be
060 * called from the commonheader.jsp.
061 * <p>
062 * This method reads user cookie preferences and mixes them up with any
063 * default preferences (and in the future, any user-specific preferences)
064 * and puts them all in the session, so that they do not have to be rewritten
065 * again.
066 * <p>
067 * This method will remember if the user has already changed his prefs.
068 *
069 * @param pageContext The JSP PageContext.
070 */
071 public static void setupPreferences( PageContext pageContext )
072 {
073 HttpSession session = pageContext.getSession();
074
075 if( session.getAttribute( SESSIONPREFS ) == null )
076 {
077 reloadPreferences( pageContext );
078 }
079 }
080
081 /**
082 * Reloads the preferences from the PageContext into the WikiContext.
083 *
084 * @param pageContext The page context.
085 */
086 // FIXME: The way that date preferences are chosen is currently a bit wacky: it all
087 // gets saved to the cookie based on the browser state with which the user
088 // happened to first arrive to the site with. This, unfortunately, means that
089 // even if the user changes e.g. language preferences (like in a web cafe),
090 // the old preferences still remain in a site cookie.
091 public static void reloadPreferences( PageContext pageContext )
092 {
093 Preferences prefs = new Preferences();
094 Properties props = PropertyReader.loadWebAppProps( pageContext.getServletContext() );
095 WikiContext ctx = WikiContext.findContext( pageContext );
096
097 prefs.put("SkinName", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.skinname", "PlainVanilla" ) );
098 prefs.put("DateFormat",
099 TextUtil.getStringProperty( props,
100 "jspwiki.defaultprefs.template.dateformat",
101 ctx.getEngine().getInternationalizationManager().get( InternationalizationManager.CORE_BUNDLE,
102 getLocale( ctx ),
103 "common.datetimeformat" ) ) );
104
105 prefs.put("TimeZone", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.timezone",
106 java.util.TimeZone.getDefault().getID() ) );
107
108 prefs.put("Orientation", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.orientation", "fav-left" ) );
109
110 prefs.put("Language", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.language",
111 getLocale( ctx ).toString() ) );
112
113 prefs.put("SectionEditing", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.sectionediting",
114 "" ) );
115
116 // FIXME: "editor" property does not get registered, may be related with http://bugs.jspwiki.org/show_bug.cgi?id=117
117 // disabling it until knowing why it's happening
118 // FIXME: editormanager reads jspwiki.editor -- which of both properties should continue
119 prefs.put("editor", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.editor", "plain" ) );
120
121 parseJSONPreferences( (HttpServletRequest) pageContext.getRequest(), prefs );
122
123 pageContext.getSession().setAttribute( SESSIONPREFS, prefs );
124 }
125
126
127 /**
128 * Parses new-style preferences stored as JSON objects and stores them
129 * in the session. Everything in the cookie is stored.
130 *
131 * @param request
132 * @param prefs The default hashmap of preferences
133 *
134 */
135 private static void parseJSONPreferences( HttpServletRequest request, Preferences prefs )
136 {
137 //FIXME: urlDecodeUTF8 should better go in HttpUtil ??
138 String prefVal = TextUtil.urlDecodeUTF8( HttpUtil.retrieveCookieValue( request, "JSPWikiUserPrefs" ) );
139
140 if( prefVal != null )
141 {
142 try
143 {
144 JSONObject jo = new JSONObject( prefVal );
145
146 for( Iterator i = jo.keys(); i.hasNext(); )
147 {
148 String key = TextUtil.replaceEntities( (String)i.next() );
149 prefs.put(key, jo.getString(key) );
150 }
151 }
152 catch( ParseException e )
153 {
154 }
155 }
156 }
157
158 /**
159 * Returns a preference value programmatically.
160 * FIXME
161 *
162 * @param wikiContext
163 * @param name
164 * @return the preference value
165 */
166 public static String getPreference( WikiContext wikiContext, String name )
167 {
168 HttpServletRequest request = wikiContext.getHttpRequest();
169 if ( request == null ) return null;
170
171 Preferences prefs = (Preferences)request.getSession().getAttribute( SESSIONPREFS );
172
173 if( prefs != null )
174 return prefs.get( name );
175
176 return null;
177 }
178 /**
179 * Returns a preference value programmatically.
180 * FIXME
181 *
182 * @param pageContext
183 * @param name
184 * @return the preference value
185 */
186 public static String getPreference( PageContext pageContext, String name )
187 {
188 Preferences prefs = (Preferences)pageContext.getSession().getAttribute( SESSIONPREFS );
189
190 if( prefs != null )
191 return prefs.get( name );
192
193 return null;
194 }
195
196
197 /**
198 * Get Locale according to user-preference settings or the user browser locale
199 *
200 * @param context The context to examine.
201 * @return a Locale object.
202 * @since 2.8
203 */
204 public static Locale getLocale( WikiContext context )
205 {
206 Locale loc = null;
207
208 String langSetting = getPreference( context, "Language" );
209
210 //
211 // parse language and construct valid Locale object
212 //
213 if( langSetting != null )
214 {
215 String language = "";
216 String country = "";
217 String variant = "";
218
219 String[] res = StringUtils.split( langSetting, "-_" );
220
221 if( res.length > 2 ) variant = res[2];
222 if( res.length > 1 ) country = res[1];
223
224 if( res.length > 0 )
225 {
226 language = res[0];
227
228 loc = new Locale( language, country, variant );
229 }
230 }
231
232 // otherwise try to find out the browser's preferred language setting, or use the JVM's default
233 if( loc == null )
234 {
235 HttpServletRequest request = context.getHttpRequest();
236 loc = ( request != null ) ? request.getLocale() : Locale.getDefault();
237 }
238
239 //log.info( "using locale "+loc.toString() );
240 return loc;
241 }
242
243 /**
244 * Locates the i18n ResourceBundle given. This method interprets
245 * the request locale, and uses that to figure out which language the
246 * user wants.
247 * @see org.apache.wiki.i18n.InternationalizationManager
248 * @param context {@link WikiContext} holding the user's locale
249 * @param bundle The name of the bundle you are looking for.
250 * @return A localized string (or from the default language, if not found)
251 * @throws MissingResourceException If the bundle cannot be found
252 */
253 public static ResourceBundle getBundle( WikiContext context, String bundle )
254 throws MissingResourceException
255 {
256 Locale loc = getLocale( context );
257 InternationalizationManager i18n = context.getEngine().getInternationalizationManager();
258 return i18n.getBundle( bundle, loc );
259 }
260
261 /**
262 * Get SimpleTimeFormat according to user browser locale and preferred time
263 * formats. If not found, it will revert to whichever format is set for the
264 * default
265 *
266 * @param context WikiContext to use for rendering.
267 * @param tf Which version of the dateformat you are looking for?
268 * @return A SimpleTimeFormat object which you can use to render
269 * @since 2.8
270 */
271 public static SimpleDateFormat getDateFormat( WikiContext context, TimeFormat tf )
272 {
273 InternationalizationManager imgr = context.getEngine().getInternationalizationManager();
274 Locale clientLocale = getLocale( context );
275 String prefTimeZone = getPreference( context, "TimeZone" );
276 String prefDateFormat;
277
278 log.debug("Checking for preferences...");
279
280 switch( tf )
281 {
282 case DATETIME:
283 prefDateFormat = getPreference( context, "DateFormat" );
284 log.debug("Preferences fmt = "+prefDateFormat);
285 if( prefDateFormat == null )
286 {
287 prefDateFormat = imgr.get( InternationalizationManager.CORE_BUNDLE,
288 clientLocale,
289 "common.datetimeformat" );
290 log.debug("Using locale-format = "+prefDateFormat);
291 }
292 break;
293
294 case TIME:
295 prefDateFormat = imgr.get( "common.timeformat" );
296 break;
297
298 case DATE:
299 prefDateFormat = imgr.get( "common.dateformat" );
300 break;
301
302 default:
303 throw new InternalWikiException( "Got a TimeFormat for which we have no value!" );
304 }
305
306 try
307 {
308 SimpleDateFormat fmt = new SimpleDateFormat( prefDateFormat, clientLocale );
309
310 if( prefTimeZone != null )
311 {
312 TimeZone tz = TimeZone.getTimeZone( prefTimeZone );
313 // TimeZone tz = TimeZone.getDefault();
314 // tz.setRawOffset(Integer.parseInt(prefTimeZone));
315
316 fmt.setTimeZone( tz );
317 }
318
319 return fmt;
320 }
321 catch( Exception e )
322 {
323 return null;
324 }
325 }
326
327 /**
328 * A simple helper function to render a date based on the user preferences.
329 * This is useful for example for all plugins.
330 *
331 * @param context The context which is used to get the preferences
332 * @param date The date to render.
333 * @param tf In which format the date should be rendered.
334 * @return A ready-rendered date.
335 * @since 2.8
336 */
337 public static String renderDate( WikiContext context, Date date, TimeFormat tf )
338 {
339 DateFormat df = getDateFormat( context, tf );
340
341 return df.format( date );
342 }
343
344 /**
345 * Is used to choose between the different date formats that JSPWiki supports.
346 * <ul>
347 * <li>TIME: A time format, without date</li>
348 * <li>DATE: A date format, without a time</li>
349 * <li>DATETIME: A date format, with a time</li>
350 * </ul>
351 *
352 * @since 2.8
353 */
354 public enum TimeFormat
355 {
356 /** A time format, no date. */
357 TIME,
358
359 /** A date format, no time. */
360 DATE,
361
362 /** A date+time format. */
363 DATETIME
364 }
365 }