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.LocaleUtils; 036import org.apache.commons.lang.StringUtils; 037import org.apache.log4j.Logger; 038import org.apache.wiki.InternalWikiException; 039import org.apache.wiki.WikiContext; 040import org.apache.wiki.i18n.InternationalizationManager; 041import org.apache.wiki.util.HttpUtil; 042import org.apache.wiki.util.PropertyReader; 043import org.apache.wiki.util.TextUtil; 044 045import com.google.gson.Gson; 046 047/** 048 * Represents an object which is used to store user preferences. 049 * 050 */ 051public class Preferences 052 extends HashMap<String,String> 053{ 054 private static final long serialVersionUID = 1L; 055 056 /** 057 * The name under which a Preferences object is stored in the HttpSession. 058 * Its value is {@value}. 059 */ 060 public static final String SESSIONPREFS = "prefs"; 061 062 private static Logger log = Logger.getLogger( Preferences.class ); 063 064 /** 065 * This is an utility method which is called to make sure that the 066 * JSP pages do have proper access to any user preferences. It should be 067 * called from the commonheader.jsp. 068 * <p> 069 * This method reads user cookie preferences and mixes them up with any 070 * default preferences (and in the future, any user-specific preferences) 071 * and puts them all in the session, so that they do not have to be rewritten 072 * again. 073 * <p> 074 * This method will remember if the user has already changed his prefs. 075 * 076 * @param pageContext The JSP PageContext. 077 */ 078 public static void setupPreferences( PageContext pageContext ) 079 { 080 //HttpSession session = pageContext.getSession(); 081 082 //if( session.getAttribute( SESSIONPREFS ) == null ) 083 //{ 084 reloadPreferences( pageContext ); 085 //} 086 } 087 088 /** 089 * Reloads the preferences from the PageContext into the WikiContext. 090 * 091 * @param pageContext The page context. 092 */ 093 // FIXME: The way that date preferences are chosen is currently a bit wacky: it all 094 // gets saved to the cookie based on the browser state with which the user 095 // happened to first arrive to the site with. This, unfortunately, means that 096 // even if the user changes e.g. language preferences (like in a web cafe), 097 // the old preferences still remain in a site cookie. 098 public static void reloadPreferences( PageContext pageContext ) 099 { 100 Preferences prefs = new Preferences(); 101 Properties props = PropertyReader.loadWebAppProps( pageContext.getServletContext() ); 102 WikiContext ctx = WikiContext.findContext( pageContext ); 103 String dateFormat = ctx.getEngine().getInternationalizationManager().get( InternationalizationManager.CORE_BUNDLE, 104 getLocale( ctx ), 105 "common.datetimeformat" ); 106 107 prefs.put("SkinName", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.skinname", "PlainVanilla" ) ); 108 prefs.put("DateFormat", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.dateformat", dateFormat ) ); 109 prefs.put("TimeZone", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.timezone", java.util.TimeZone.getDefault().getID() ) ); 110 prefs.put("Orientation", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.orientation", "fav-left" ) ); 111 prefs.put("Sidebar", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.sidebar", "active" ) ); 112 prefs.put("Layout", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.layout", "fluid" ) ); 113 prefs.put("Language", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.language", getLocale( ctx ).toString() ) ); 114 prefs.put("SectionEditing", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.sectionediting", "true" ) ); 115 prefs.put("Appearance", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.appearance", "true" ) ); 116 117 // FIXME: editormanager reads jspwiki.editor -- which of both properties should continue 118 prefs.put("editor", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.editor", "plain" ) ); 119 120 parseJSONPreferences( (HttpServletRequest) pageContext.getRequest(), prefs ); 121 122 pageContext.getSession().setAttribute( SESSIONPREFS, prefs ); 123 } 124 125 126 /** 127 * Parses new-style preferences stored as JSON objects and stores them 128 * in the session. Everything in the cookie is stored. 129 * 130 * @param request 131 * @param prefs The default hashmap of preferences 132 * 133 */ 134 private static void parseJSONPreferences( HttpServletRequest request, Preferences prefs ) { 135 String prefVal = TextUtil.urlDecodeUTF8( HttpUtil.retrieveCookieValue( request, "JSPWikiUserPrefs" ) ); 136 137 if( prefVal != null ) { 138 // Convert prefVal JSON to a generic hashmap 139 @SuppressWarnings("unchecked") 140 Map<String,String> map = new Gson().fromJson(prefVal, Map.class ); 141 142 for (String key : map.keySet()) { 143 key = TextUtil.replaceEntities( key ); 144 // Sometimes this is not a String as it comes from the Cookie set by Javascript 145 Object value = map.get(key); 146 if (value != null) { 147 prefs.put( key, value.toString() ); 148 } 149 } 150 } 151 } 152 153 /** 154 * Returns a preference value programmatically. 155 * FIXME 156 * 157 * @param wikiContext 158 * @param name 159 * @return the preference value 160 */ 161 public static String getPreference( WikiContext wikiContext, String name ) { 162 HttpServletRequest request = wikiContext.getHttpRequest(); 163 if ( request == null ) return null; 164 165 Preferences prefs = (Preferences)request.getSession().getAttribute( SESSIONPREFS ); 166 167 if( prefs != null ) { 168 return prefs.get( name ); 169 } 170 171 return null; 172 } 173 174 /** 175 * Returns a preference value programmatically. 176 * FIXME 177 * 178 * @param pageContext 179 * @param name 180 * @return the preference value 181 */ 182 public static String getPreference( PageContext pageContext, String name ) 183 { 184 Preferences prefs = (Preferences)pageContext.getSession().getAttribute( SESSIONPREFS ); 185 186 if( prefs != null ) 187 return prefs.get( name ); 188 189 return null; 190 } 191 192 193 /** 194 * Get Locale according to user-preference settings or the user browser locale 195 * 196 * @param context The context to examine. 197 * @return a Locale object. 198 * @since 2.8 199 */ 200 public static Locale getLocale( WikiContext context ) { 201 Locale loc = null; 202 203 String langSetting = getPreference( context, "Language" ); 204 205 // parse language and construct valid Locale object 206 if( langSetting != null ) { 207 String language = ""; 208 String country = ""; 209 String variant = ""; 210 211 String[] res = StringUtils.split( langSetting, "-_" ); 212 213 if( res.length > 2 ) variant = res[2]; 214 if( res.length > 1 ) country = res[1]; 215 216 if( res.length > 0 ) { 217 language = res[0]; 218 219 loc = new Locale( language, country, variant ); 220 } 221 } 222 223 // see if default locale is set server side 224 if( loc == null ) { 225 String locale = context.getEngine().getWikiProperties().getProperty( "jspwiki.preferences.default-locale" ); 226 try { 227 loc = LocaleUtils.toLocale( locale ); 228 } catch( IllegalArgumentException iae ) { 229 log.error( iae.getMessage() ); 230 } 231 } 232 233 // otherwise try to find out the browser's preferred language setting, or use the JVM's default 234 if( loc == null ) { 235 HttpServletRequest request = context.getHttpRequest(); 236 loc = ( request != null ) ? request.getLocale() : Locale.getDefault(); 237 } 238 239 log.debug( "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}