001package 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.
002
003import org.apache.wicket.*;
004import org.apache.wicket.markup.html.*;
005import org.apache.wicket.markup.transformer.*;
006import org.apache.wicket.markup.html.panel.*;
007import org.apache.wicket.request.cycle.RequestCycle;
008import org.apache.wicket.request.mapper.parameter.PageParameters;
009import votorola.a.*;
010import votorola.g.lang.*;
011import votorola.g.mail.WicEmailAddressValidator;
012import votorola.g.web.wic.*;
013
014
015/** A page in the Wicket web interface.
016  *
017  * <h4>Thread safety</h4>
018  *
019  * <p>Access to pages, components and their models is restricted to Wicket threads.  This
020  * restriction is documented by the annotation <code>@{@linkplain ThreadRestricted
021  * ThreadRestricted}("wicket")</code>.  Runtime enforcement of compliance is not
022  * possible, e.g. by the use of assertions, because Wicket threads are synchronized
023  * through a locking mechanism that is private to Wicket.
024  * See <a href='http://www.nabble.com/Thread-safety-for-components-to17265324.html'
025  *                    >www.nabble.com/Thread-safety-for-components-to17265324</a>.</p>
026  *
027  * <h4>Form processing</h4>
028  *
029  * <p>Note that when Wicket's form processor (1.3.2) is accessing a field, it has the
030  * habit of bypassing accessor methods and reading/writing the field directly, even if it
031  * happens to be declared private.  To prevent this, ensure that any field a form might
032  * want to access is provided with an accessor method that is declared public (package is
033  * not suffient).</p>
034  *
035  * <h4>Page state and versioning</h4>
036  *
037  * <p>Numeric version parameters were added to the URL of each stateful page in 1.5.  The
038  * standard workaround for avoiding these is to make the page <a
039  * href='https://cwiki.apache.org/WICKET/stateless-pages.html'
040  * target='_top'>stateless</a>.  (Use {@linkplain
041  * org.apache.wicket.devutils.stateless.StatelessComponent StatelessComponent} to enable
042  * debugging of this in development mode.)  Constructors and onClick() handlers for
043  * stateless pages need careful design, otherwise servicing a single click may require a
044  * heavy reconstruction of the page.</p>
045  */
046public abstract @ThreadRestricted("wicket") class VPage extends WebPage
047{
048    // It is not yet clear (having not looked into the code) whether Wicket is ensuring
049    // that the local memory caches of pooled threads are synchronized through main
050    // memory.  Assuming that Wicket does use a pool for its request threads, to be fully
051    // compliant with Java's memory model it must synchronize each thread on a common lock
052    // both before and after processing the request, thus invalidating and flushing the
053    // caches, in order to guarantee cross-thread visibility of state changes.  But we can
054    // just assume this for now, as it should be easy enough to correct later.
055
056
057    /** Constructs a VPage, per {@linkplain WebPage#WebPage() WebPage}().
058      */
059    protected VPage() {}
060
061
062
063    /** Constructs a VPage, per {@linkplain WebPage#WebPage(PageParameters) WebPage}(_pP).
064      */
065    protected VPage( PageParameters _pP ) { super( _pP ); }
066
067
068
069   // ------------------------------------------------------------------------------------
070
071
072    /** A shared instance of a validator for strict email addresses.
073      */
074    public static WicEmailAddressValidator emailAddressValidator() { return emailAddressValidator; }
075
076
077        private static final WicEmailAddressValidator emailAddressValidator =
078          new WicEmailAddressValidator();
079
080
081
082    /** A shared instance of a validator for standard input length.  This is not required
083      * if the input length is already constrained by view attributes and model (or
084      * converter) guards.
085      *
086      *     @see VoterInputTable#MAX_INPUT_LENGTH
087      */
088    public static MaxLengthValidator inputLengthValidator() { return inputLengthValidator; }
089
090
091        private static final MaxLengthValidator inputLengthValidator =
092          new MaxLengthValidator( VoterInputTable.MAX_INPUT_LENGTH );
093
094
095
096    /** Constructs a fragment that renders only its body, no tags.
097      */
098    public static Fragment newBodyOnlyFragment( final String id, final String markupID,
099      final MarkupContainer markupProvider )
100    {
101        final Fragment f = new Fragment( id, markupID, markupProvider );
102        f.setRenderBodyOnly( true );
103        return f;
104    }
105
106
107
108    /** Constructs a component that is invisible and does nothing.
109      */
110    public static MarkupContainer newNullComponent( final String id )
111    {
112        final MarkupContainer c = new NoopOutputTransformerContainer( id );
113        c.setVisible( false );
114        return c;
115    }
116
117
118
119    /** The maximum length of a 'short string', in characters.
120      */
121    public static final int SHORT_STRING_LENGTH_MAX = 16;
122
123
124
125    /** Returns a version of the string that is truncated to {@linkplain
126      * #SHORT_STRING_LENGTH_MAX SHORT_STRING_LENGTH_MAX} characters.
127      *
128      *     @return truncated version of string; or the same string,
129      *       if no truncation is required
130      */
131    public static @ThreadSafe String shortened( String string )
132    {
133        if( string.length() > SHORT_STRING_LENGTH_MAX )
134        {
135            string = string.substring(0,SHORT_STRING_LENGTH_MAX-1) + ELLIPSIS;
136        }
137        return string;
138    }
139
140
141        private static final char ELLIPSIS = /*horizontal ellipsis*/'\u2026';
142     // private static final char ELLIPSIS = '/';
143
144
145
146    /** Returns a version of the string, in which each of the whitespace delimited words
147      * is truncated to {@linkplain #SHORT_STRING_LENGTH_MAX SHORT_STRING_LENGTH_MAX}
148      * characters; or the same string, if no truncation is required.
149      */
150    public static @ThreadSafe String shortenedWords( final String string )
151    {
152        StringBuilder sB = null; // till needed
153        for( int c = 0, cN = string.length(), characterCount = 0; c < cN; ++c )
154        {
155            final char ch = string.charAt( c );
156            if( Character.isWhitespace( ch ))
157            {
158                if( sB != null ) sB.append( ch );
159                characterCount = 0;
160                continue;
161            }
162
163            if( characterCount < SHORT_STRING_LENGTH_MAX )
164            {
165                if( sB != null ) sB.append( ch );
166            }
167            else if( characterCount == SHORT_STRING_LENGTH_MAX ) // just passing the limit
168            {
169                if( sB == null )
170                {
171                    sB = new StringBuilder( cN );
172                    sB.append( string, 0, c - 1 );
173                    sB.append( ELLIPSIS );
174                }
175                else sB.setCharAt( sB.length() - 1, ELLIPSIS );
176            }
177            // else already passed limit, simply ignore
178            ++characterCount;
179        }
180
181        return sB == null? string: sB.toString();
182    }
183
184
185
186    /** Retrieves a null or non-empty string value by key.
187      *
188      *   @throws RestartResponseException if the value is the empty string "", and
189      *     registers an error with the session.
190      *
191      *   @see votorola.g.web.HTTPServletRequestX#getParameterNonEmpty(String,javax.servlet.ServletRequest)
192      */
193    public static String stringNonEmpty( final PageParameters pP, final String key )
194    {
195        final String value = PageParametersX.getString( pP, key );
196        if( !"".equals( value )) return value;
197
198        Session.get().error( "empty query parameter '" + key + "'" );
199        throw new RestartResponseException( new WP_Message() );
200    }
201
202
203
204    /** Retrieves a non-null, non-empty string value by key.
205      *
206      *   @throws RestartResponseException if the value cannot be retrieved, and registers
207      *     an error with the session.
208      *
209      *   @see votorola.g.web.HTTPServletRequestX#getParameterRequired(String,javax.servlet.ServletRequest)
210      */
211    public static String stringRequired( final PageParameters pP, final String key )
212    {
213        final String value = PageParametersX.getString( pP, key );
214        if( value != null && !"".equals( value )) return value;
215
216        Session.get().error( "missing query parameter '" + key + "'" );
217        throw new RestartResponseException( new WP_Message() );
218    }
219
220
221
222    /** @see #getApplication()
223      */ // rather than override getApplication(), because some of these getX methods are
224         // final, so use vX for all
225    protected final VOWicket vApplication() { return (VOWicket)getApplication(); } /* ignore sporadic [cast]
226      redundancy warning.  Left out it fails anyway (?compiler bug in 1.5-1.7) */
227
228
229
230    /** @see #getRequestCycle()
231      * @see VRequestCycle#get()
232      */ // "
233    protected final VRequestCycle vRequestCycle() { return (VRequestCycle)getRequestCycle(); } /* ignore sporadic [cast]
234      redundancy warning here, too. */
235
236
237
238}