package votorola.a.web.wic; // Copyright 2008-2012, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. import javax.servlet.http.*; import org.apache.wicket.*; import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.markup.MarkupType; import org.apache.wicket.markup.html.IHeaderResponse; import org.apache.wicket.markup.html.basic.*; import org.apache.wicket.markup.html.form.*; import org.apache.wicket.markup.html.navigation.paging.*; import org.apache.wicket.markup.repeater.data.DataView; import org.apache.wicket.request.http.WebRequest; import org.apache.wicket.request.http.WebResponse; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.model.*; import org.apache.wicket.util.time.Duration; import votorola.a.*; import votorola.a.web.wic.authen.*; import votorola.a.voter.*; import votorola.g.lang.*; import votorola.g.web.*; import votorola.g.web.wic.*; /** An HTML page in the Wicket web interface. The page defines the following JavaScript * variables for the use of client scripts: * * * * * * * * * * * *
NameValue
voc.pageJClassThe class name of the specific Java type that implements this page. The * name is fully qualified.
*/ public abstract @ThreadRestricted("wicket") class VPageHTML extends VPage { /** Constructs a VPageHTML, per {@linkplain * org.apache.wicket.markup.html.WebPage#WebPage() WebPage}(). */ protected VPageHTML() {} /** Constructs a VPageHTML, per {@linkplain * org.apache.wicket.markup.html.WebPage#WebPage(PageParameters) WebPage}(_pP). */ protected VPageHTML( PageParameters _pP ) { super( _pP ); } // ------------------------------------------------------------------------------------ /** Adds a cookie to convey the user's login state to cacheable pages. Client side * script VPageHTML.js * compares the value of this cookie against that * encoded in the page and automatically reloads the page to correct the rendering * when necessary. This applies only to cacheable pages and is employed as a * temporary workaround pending the movement of login dependent rendering to the * client side. * * @param contextPath the context path of the request, which will also be used as * the cookie path. * * @see #encodeSessionStateCookieValue(votorola.a.web.wic.VSession.User) * @see #COOKIE_SESSION_STATE */ static void addSessionStateCookie( final String encodedValue, final String contextPath, final WebResponse resW ) { final CookieX cookie = new CookieX( COOKIE_SESSION_STATE, encodedValue, /*toEncode*/false ); cookie.setMaxAge( CookieX.DURATION_YEAR_S ); // only cost of loss is browser does a refresh cookie.setPath( contextPath ); // allow access from pages other than the current one WebResponseX.addCookie( resW, cookie ); } /** Appends a CSS class identifier to the component's 'class' attribute. Uses an * AttributeAppender for this purpose. If you wish to remove the identifier later, * then use your own AttributeAppender and override its isEnabled() method. * * @return the component */ public static C appendStyleClass( final C component, final String classIdentifier ) { component.add( new AttributeAppender( "class", new Model(classIdentifier), " " )); return component; } /** The name of the cookie for transmitting the state of the session to cacheable * pages. * * @see #addSessionStateCookie(String,String,WebResponse) */ static final String COOKIE_SESSION_STATE = "vo_sessionState"; /** Encodes a value suitable for a session state cookie. * * @see #addSessionStateCookie(String,String,WebResponse) */ static String encodeSessionStateCookieValue( final VSession.User user ) { return user == null? "out": CookieX.encodedValue( user.username() ); // login state is all that cacheable pages depend on } /** The duration for client-side caching, where enabled. The default value of one * hour is suitable for pages that contain voting results. * * @see #isCacheable() * @see #setCacheDuration(Duration) */ protected final Duration getCacheDuration() { return cacheDuration; } private Duration cacheDuration = Duration.ONE_HOUR; /** A duration of roughly one year, which is suitable as a cache duration for * pages that have stable content. * * @see #getCacheDuration() */ public static final Duration CACHE_DURATION_YEAR = Duration.days( 365 ); /** Sets the duration for client-side caching, where enabled. * * @see #getCacheDuration() * @see #CACHE_DURATION_YEAR */ protected final void setCacheDuration( final Duration d ) { cacheDuration = d; } /** The location of the page icon. * * @see VOWicket#defaultPageIcon() */ protected final String getPageIcon() { return pageIcon; } private String pageIcon = VOWicket.get().defaultPageIcon().toString(); /** Sets the location of the page icon. */ protected final void setPageIcon( String s ) { pageIcon = s; } /** Constrains a CharSequence text field to the standard input length. Wraps a length * limiter around its model, and adds a 'maxLength' attribute to its view. For use * only with a field that is backed by a character sequence model. (For others, the * associated IConverter should enforce its own constraints at conversion time.) * * @return the same field * * @see ModelLengthLimiter * @see VoterInputTable#MAX_INPUT_LENGTH * @see #inputLengthValidator() */ public static > C inputLengthConstrained( final C field ) { field.setModel( new ModelLengthLimiter( field.getModel(), VoterInputTable.MAX_INPUT_LENGTH )); field.add( AttributeModifier.replace( "maxlength", Integer.toString( VoterInputTable.MAX_INPUT_LENGTH ))); return field; } /** Trains the form component to aquire the style class 'invalid', whenever it fails * validation. * * @return the component */ public static > C invalidStyled( final C component ) { component.add( invalidStyler ); return component; } /** For use on a FormComponent, only. */ private static final AttributeAppender invalidStyler = new AttributeAppender( "class", new Model("invalid"), " " ) { public @Override boolean isEnabled( final Component component ) { return super.isEnabled(component) && !((FormComponent)component).isValid(); } }; /** Answers whether client-side caching is enabled. When enabled, the response will * allow the client to cache this page privately for the cache duration; otherwise it * will forbid caching. The default value is false. * *

Cacheable pages may depend on the user's login state. A {@linkplain * #addSessionStateCookie(String,String,WebResponse) cookie/script mechanism} is used * to automatically reload cached pages and bypass the cache following any change to * login state. The rendering of the page may therefore depend on the identity of * the authenticated user, but not on any other crucial variable that might change over * the course of the cache duration.

* * @see #setCacheable(boolean) * @see #getCacheDuration() */ protected final boolean isCacheable() { return isCacheable; } private boolean isCacheable; /** Sets whether client-side caching is enabled. * * @see #isCacheable() */ protected final void setCacheable( final boolean is ) { isCacheable = is; } /** Constructs a label containing a non-breaking space character ( ). */ public static Label newLabelNBSP( String id ) { final Label label = new Label( id, " " ); label.setEscapeModelStrings( false ); return label; } /** Constructs a label containing a non-breaking space character, for situations that * preclude using a proper null component. One such situation is the end of the * page, where a null component somehow causes clipping of the content above (IE7). */ public static Component newNullComponentAsLabel( String id ) { return newLabelNBSP( id ); } /** Constructs a paging navigator with a standard configuration. */ public static PagingNavigator newPagingNavigator( final String id, final DataView dataView ) { final PagingNavigator navigator = new PagingNavigator( id, dataView ) // navigator.getPagingNavigation().setViewSize( 5 ); //// getPagingNavigation() is null till after onBeforeRender() (since upgrade from 1.3.2 to 1.3.7) { protected PagingNavigation newNavigation( final String idPN, final IPageable pageable, final IPagingLabelProvider labelProvider ) { final PagingNavigation pN = super.newNavigation( idPN, pageable, labelProvider ); pN.setViewSize( 5 ); // page link short cuts, at a time (rather than default 10) return pN; } }; return navigator; } // - I - H e a d e r - C o n t r i b u t o r ------------------------------------------ public @Override void renderHead( final IHeaderResponse r ) { assert MarkupType.HTML_MARKUP_TYPE.equals( getMarkupType() ); final VRequestCycle cycle = VRequestCycle.get(); final WebRequest reqW = cycle.vRequest(); final String contextPath = reqW.getContextPath(); // Define JavaScript variables for general use on client side ... // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - final StringBuilder b = new StringBuilder(); b.append( "var voc =" // namespace for public, client-side variables + "{" ); b.append( "pageJClass:'" ).append( getClass().getName() ).append( '\'' ); // ... and specifically for use in VPageHTML.js. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - final String state; b.append( ",contextPath:'" ).append( contextPath ).append( '\'' ); if( isCacheable ) { final VSession.User user = VSession.get().user(); state = encodeSessionStateCookieValue( user ); final Cookie cookie = reqW.getCookie( COOKIE_SESSION_STATE ); if( cookie != null && "refreshing".equals(cookie.getValue()) ) { // clear the loop guard, per VPageHTML.js addSessionStateCookie( state, contextPath, cycle.vResponse() ); } final Authenticator authenticator = VOWicket.get().authenticator(); if( authenticator instanceof WikiAuthenticator ) { if( user != null && user.isPersistent() ) b.append( ",isPersistentLogin:true" ); b.append( ",pollwikiCookiePrefix:'" ).append( ((WikiAuthenticator)authenticator).getCookiePrefix() ).append( '\'' ); } } else state = "ignore"; // tell VPageHTML.js not to bother synchronizing this page b.append( ",sessionState:'" ).append( state ).append( '\'' ); b.append( "};" ); r.renderJavaScript( b.toString(), /*unique ID for rendering*/VPageHTML.class.getName() ); // Inject VPageHTML.js ASAP, because it may force a reload of the page. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - r.renderJavaScriptReference( cycle.staticContextLocation() + "/web/VPageHTML.js" ); // Ensure Wicket provides its Mootools-like extended 'domready' event. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - r.renderOnDomReadyJavaScript( "/* forcing inclusion of wicket-event.js */" ); // resources/org.apache.wicket.markup.html.WicketEventReference/wicket-event.js // Add a page icon. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - r.renderString( "\n" // + "\n" /// fails, I get an IE default logo ); // - - - final String htmlHeaderInsert = vApplication().htmlHeaderInsert(); if( htmlHeaderInsert != null ) r.renderString( htmlHeaderInsert ); } // - W e b - P a g e ------------------------------------------------------------------ /** Sets headers to enable or disable caching. * * @see #isCacheable() */ protected @Override final void setHeaders( final WebResponse r ) { if( isCacheable ) { r.enableCaching( cacheDuration, WebResponse.CacheScope.PRIVATE ); // This alone does not enable caching. As noted in WebResponseX, we must also // avoid calling super.setHeaders(r). } else super.setHeaders( r ); // disables caching, as documented } }