package votorola.a.web.wic; // Copyright 2008-2009, 2011-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 org.apache.wicket.*; import org.apache.wicket.markup.html.*; import org.apache.wicket.markup.transformer.*; import org.apache.wicket.markup.html.panel.*; import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.mapper.parameter.PageParameters; import votorola.a.*; import votorola.g.lang.*; import votorola.g.mail.WicEmailAddressValidator; import votorola.g.web.wic.*; /** A page in the Wicket web interface. * *

Thread safety

* *

Access to pages, components and their models is restricted to Wicket threads. This * restriction is documented by the annotation @{@linkplain ThreadRestricted * ThreadRestricted}("wicket"). Runtime enforcement of compliance is not * possible, e.g. by the use of assertions, because Wicket threads are synchronized * through a locking mechanism that is private to Wicket. * See www.nabble.com/Thread-safety-for-components-to17265324.

* *

Form processing

* *

Note that when Wicket's form processor (1.3.2) is accessing a field, it has the * habit of bypassing accessor methods and reading/writing the field directly, even if it * happens to be declared private. To prevent this, ensure that any field a form might * want to access is provided with an accessor method that is declared public (package is * not suffient).

* *

Page state and versioning

* *

Numeric version parameters were added to the URL of each stateful page in 1.5. The * standard workaround for avoiding these is to make the page stateless. (Use {@linkplain * org.apache.wicket.devutils.stateless.StatelessComponent StatelessComponent} to enable * debugging of this in development mode.) Constructors and onClick() handlers for * stateless pages need careful design, otherwise servicing a single click may require a * heavy reconstruction of the page.

*/ public abstract @ThreadRestricted("wicket") class VPage extends WebPage { // It is not yet clear (having not looked into the code) whether Wicket is ensuring // that the local memory caches of pooled threads are synchronized through main // memory. Assuming that Wicket does use a pool for its request threads, to be fully // compliant with Java's memory model it must synchronize each thread on a common lock // both before and after processing the request, thus invalidating and flushing the // caches, in order to guarantee cross-thread visibility of state changes. But we can // just assume this for now, as it should be easy enough to correct later. /** Constructs a VPage, per {@linkplain WebPage#WebPage() WebPage}(). */ protected VPage() {} /** Constructs a VPage, per {@linkplain WebPage#WebPage(PageParameters) WebPage}(_pP). */ protected VPage( PageParameters _pP ) { super( _pP ); } // ------------------------------------------------------------------------------------ /** A shared instance of a validator for strict email addresses. */ public static WicEmailAddressValidator emailAddressValidator() { return emailAddressValidator; } private static final WicEmailAddressValidator emailAddressValidator = new WicEmailAddressValidator(); /** A shared instance of a validator for standard input length. This is not required * if the input length is already constrained by view attributes and model (or * converter) guards. * * @see VoterInputTable#MAX_INPUT_LENGTH */ public static MaxLengthValidator inputLengthValidator() { return inputLengthValidator; } private static final MaxLengthValidator inputLengthValidator = new MaxLengthValidator( VoterInputTable.MAX_INPUT_LENGTH ); /** Constructs a fragment that renders only its body, no tags. */ public static Fragment newBodyOnlyFragment( final String id, final String markupID, final MarkupContainer markupProvider ) { final Fragment f = new Fragment( id, markupID, markupProvider ); f.setRenderBodyOnly( true ); return f; } /** Constructs a component that is invisible and does nothing. */ public static MarkupContainer newNullComponent( final String id ) { final MarkupContainer c = new NoopOutputTransformerContainer( id ); c.setVisible( false ); return c; } /** The maximum length of a 'short string', in characters. */ public static final int SHORT_STRING_LENGTH_MAX = 16; /** Returns a version of the string that is truncated to {@linkplain * #SHORT_STRING_LENGTH_MAX SHORT_STRING_LENGTH_MAX} characters. * * @return truncated version of string; or the same string, * if no truncation is required */ public static @ThreadSafe String shortened( String string ) { if( string.length() > SHORT_STRING_LENGTH_MAX ) { string = string.substring(0,SHORT_STRING_LENGTH_MAX-1) + ELLIPSIS; } return string; } private static final char ELLIPSIS = /*horizontal ellipsis*/'\u2026'; // private static final char ELLIPSIS = '/'; /** Returns a version of the string, in which each of the whitespace delimited words * is truncated to {@linkplain #SHORT_STRING_LENGTH_MAX SHORT_STRING_LENGTH_MAX} * characters; or the same string, if no truncation is required. */ public static @ThreadSafe String shortenedWords( final String string ) { StringBuilder sB = null; // till needed for( int c = 0, cN = string.length(), characterCount = 0; c < cN; ++c ) { final char ch = string.charAt( c ); if( Character.isWhitespace( ch )) { if( sB != null ) sB.append( ch ); characterCount = 0; continue; } if( characterCount < SHORT_STRING_LENGTH_MAX ) { if( sB != null ) sB.append( ch ); } else if( characterCount == SHORT_STRING_LENGTH_MAX ) // just passing the limit { if( sB == null ) { sB = new StringBuilder( cN ); sB.append( string, 0, c - 1 ); sB.append( ELLIPSIS ); } else sB.setCharAt( sB.length() - 1, ELLIPSIS ); } // else already passed limit, simply ignore ++characterCount; } return sB == null? string: sB.toString(); } /** Retrieves a null or non-empty string value by key. * * @throws RestartResponseException if the value is the empty string "", and * registers an error with the session. * * @see votorola.g.web.HTTPServletRequestX#getParameterNonEmpty(String,javax.servlet.ServletRequest) */ public static String stringNonEmpty( final PageParameters pP, final String key ) { final String value = PageParametersX.getString( pP, key ); if( !"".equals( value )) return value; Session.get().error( "empty query parameter '" + key + "'" ); throw new RestartResponseException( new WP_Message() ); } /** Retrieves a non-null, non-empty string value by key. * * @throws RestartResponseException if the value cannot be retrieved, and registers * an error with the session. * * @see votorola.g.web.HTTPServletRequestX#getParameterRequired(String,javax.servlet.ServletRequest) */ public static String stringRequired( final PageParameters pP, final String key ) { final String value = PageParametersX.getString( pP, key ); if( value != null && !"".equals( value )) return value; Session.get().error( "missing query parameter '" + key + "'" ); throw new RestartResponseException( new WP_Message() ); } /** @see #getApplication() */ // rather than override getApplication(), because some of these getX methods are // final, so use vX for all protected final VOWicket vApplication() { return (VOWicket)getApplication(); } /* ignore sporadic [cast] redundancy warning. Left out it fails anyway (?compiler bug in 1.5-1.7) */ /** @see #getRequestCycle() * @see VRequestCycle#get() */ // " protected final VRequestCycle vRequestCycle() { return (VRequestCycle)getRequestCycle(); } /* ignore sporadic [cast] redundancy warning here, too. */ }