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