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}