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 //editor cookies 118 prefs.put("autosuggest", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.autosuggest", "true" ) ); 119 prefs.put("tabcompletion", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.tabcompletion", "true" ) ); 120 prefs.put("smartpairs", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.smartpairs", "false" ) ); 121 prefs.put("livepreview", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.livepreview", "true" ) ); 122 prefs.put("previewcolumn", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.previewcolumn", "true" ) ); 123 124 125 // FIXME: editormanager reads jspwiki.editor -- which of both properties should continue 126 prefs.put("editor", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.editor", "plain" ) ); 127 128 parseJSONPreferences( (HttpServletRequest) pageContext.getRequest(), prefs ); 129 130 pageContext.getSession().setAttribute( SESSIONPREFS, prefs ); 131 } 132 133 134 /** 135 * Parses new-style preferences stored as JSON objects and stores them 136 * in the session. Everything in the cookie is stored. 137 * 138 * @param request 139 * @param prefs The default hashmap of preferences 140 * 141 */ 142 private static void parseJSONPreferences( HttpServletRequest request, Preferences prefs ) { 143 String prefVal = TextUtil.urlDecodeUTF8( HttpUtil.retrieveCookieValue( request, "JSPWikiUserPrefs" ) ); 144 145 if( prefVal != null ) { 146 // Convert prefVal JSON to a generic hashmap 147 @SuppressWarnings("unchecked") 148 Map<String,String> map = new Gson().fromJson(prefVal, Map.class ); 149 150 for (String key : map.keySet()) { 151 key = TextUtil.replaceEntities( key ); 152 // Sometimes this is not a String as it comes from the Cookie set by Javascript 153 Object value = map.get(key); 154 if (value != null) { 155 prefs.put( key, value.toString() ); 156 } 157 } 158 } 159 } 160 161 /** 162 * Returns a preference value programmatically. 163 * FIXME 164 * 165 * @param wikiContext 166 * @param name 167 * @return the preference value 168 */ 169 public static String getPreference( WikiContext wikiContext, String name ) { 170 HttpServletRequest request = wikiContext.getHttpRequest(); 171 if ( request == null ) return null; 172 173 Preferences prefs = (Preferences)request.getSession().getAttribute( SESSIONPREFS ); 174 175 if( prefs != null ) { 176 return prefs.get( name ); 177 } 178 179 return null; 180 } 181 182 /** 183 * Returns a preference value programmatically. 184 * FIXME 185 * 186 * @param pageContext 187 * @param name 188 * @return the preference value 189 */ 190 public static String getPreference( PageContext pageContext, String name ) 191 { 192 Preferences prefs = (Preferences)pageContext.getSession().getAttribute( SESSIONPREFS ); 193 194 if( prefs != null ) 195 return prefs.get( name ); 196 197 return null; 198 } 199 200 201 /** 202 * Get Locale according to user-preference settings or the user browser locale 203 * 204 * @param context The context to examine. 205 * @return a Locale object. 206 * @since 2.8 207 */ 208 public static Locale getLocale( WikiContext context ) { 209 Locale loc = null; 210 211 String langSetting = getPreference( context, "Language" ); 212 213 // parse language and construct valid Locale object 214 if( langSetting != null ) { 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 language = res[0]; 226 227 loc = new Locale( language, country, variant ); 228 } 229 } 230 231 // see if default locale is set server side 232 if( loc == null ) { 233 String locale = context.getEngine().getWikiProperties().getProperty( "jspwiki.preferences.default-locale" ); 234 try { 235 loc = LocaleUtils.toLocale( locale ); 236 } catch( IllegalArgumentException iae ) { 237 log.error( iae.getMessage() ); 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 HttpServletRequest request = context.getHttpRequest(); 244 loc = ( request != null ) ? request.getLocale() : Locale.getDefault(); 245 } 246 247 log.debug( "using locale "+loc.toString() ); 248 return loc; 249 } 250 251 /** 252 * Locates the i18n ResourceBundle given. This method interprets 253 * the request locale, and uses that to figure out which language the 254 * user wants. 255 * @see org.apache.wiki.i18n.InternationalizationManager 256 * @param context {@link WikiContext} holding the user's locale 257 * @param bundle The name of the bundle you are looking for. 258 * @return A localized string (or from the default language, if not found) 259 * @throws MissingResourceException If the bundle cannot be found 260 */ 261 public static ResourceBundle getBundle( WikiContext context, String bundle ) 262 throws MissingResourceException 263 { 264 Locale loc = getLocale( context ); 265 InternationalizationManager i18n = context.getEngine().getInternationalizationManager(); 266 return i18n.getBundle( bundle, loc ); 267 } 268 269 /** 270 * Get SimpleTimeFormat according to user browser locale and preferred time 271 * formats. If not found, it will revert to whichever format is set for the 272 * default 273 * 274 * @param context WikiContext to use for rendering. 275 * @param tf Which version of the dateformat you are looking for? 276 * @return A SimpleTimeFormat object which you can use to render 277 * @since 2.8 278 */ 279 public static SimpleDateFormat getDateFormat( WikiContext context, TimeFormat tf ) 280 { 281 InternationalizationManager imgr = context.getEngine().getInternationalizationManager(); 282 Locale clientLocale = getLocale( context ); 283 String prefTimeZone = getPreference( context, "TimeZone" ); 284 String prefDateFormat; 285 286 log.debug("Checking for preferences..."); 287 288 switch( tf ) 289 { 290 case DATETIME: 291 prefDateFormat = getPreference( context, "DateFormat" ); 292 log.debug("Preferences fmt = "+prefDateFormat); 293 if( prefDateFormat == null ) 294 { 295 prefDateFormat = imgr.get( InternationalizationManager.CORE_BUNDLE, 296 clientLocale, 297 "common.datetimeformat" ); 298 log.debug("Using locale-format = "+prefDateFormat); 299 } 300 break; 301 302 case TIME: 303 prefDateFormat = imgr.get( "common.timeformat" ); 304 break; 305 306 case DATE: 307 prefDateFormat = imgr.get( "common.dateformat" ); 308 break; 309 310 default: 311 throw new InternalWikiException( "Got a TimeFormat for which we have no value!" ); 312 } 313 314 try 315 { 316 SimpleDateFormat fmt = new SimpleDateFormat( prefDateFormat, clientLocale ); 317 318 if( prefTimeZone != null ) 319 { 320 TimeZone tz = TimeZone.getTimeZone( prefTimeZone ); 321 // TimeZone tz = TimeZone.getDefault(); 322 // tz.setRawOffset(Integer.parseInt(prefTimeZone)); 323 324 fmt.setTimeZone( tz ); 325 } 326 327 return fmt; 328 } 329 catch( Exception e ) 330 { 331 return null; 332 } 333 } 334 335 /** 336 * A simple helper function to render a date based on the user preferences. 337 * This is useful for example for all plugins. 338 * 339 * @param context The context which is used to get the preferences 340 * @param date The date to render. 341 * @param tf In which format the date should be rendered. 342 * @return A ready-rendered date. 343 * @since 2.8 344 */ 345 public static String renderDate( WikiContext context, Date date, TimeFormat tf ) 346 { 347 DateFormat df = getDateFormat( context, tf ); 348 349 return df.format( date ); 350 } 351 352 /** 353 * Is used to choose between the different date formats that JSPWiki supports. 354 * <ul> 355 * <li>TIME: A time format, without date</li> 356 * <li>DATE: A date format, without a time</li> 357 * <li>DATETIME: A date format, with a time</li> 358 * </ul> 359 * 360 * @since 2.8 361 */ 362 public enum TimeFormat 363 { 364 /** A time format, no date. */ 365 TIME, 366 367 /** A date format, no time. */ 368 DATE, 369 370 /** A date+time format. */ 371 DATETIME 372 } 373}