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