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 }