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