001package votorola.a; // Copyright 2008-2013, 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 java.io.*; 004import java.net.*; 005import java.util.logging.*; import votorola.g.logging.*; 006import javax.ws.rs.core.UriBuilder; 007import javax.xml.stream.*; 008import votorola.a.position.*; 009import votorola.g.*; 010import votorola.g.hold.*; 011import votorola.g.lang.*; 012 013 014/** A pollwiki from the viewpoint of a vote-server. 015 * 016 * @see VoteServer#pollwiki() 017 * @see <a href='../../../../s/manual.xht#wiki' target='_top' 018 * >../s/manual.xht#wiki</a> 019 */ 020public @ThreadSafe final class PollwikiVS implements Pollwiki 021{ 022 023 024 /** Partially constructs a PollwikiVS for {@linkplain #init(VoteServer) init} to finish. 025 */ 026 PollwikiVS( final ConstructionContext cc ) throws URISyntaxException 027 { 028 uri = cc.getURI(); 029 password = cc.getPassword(); 030 revisionSeries = cc.getRevisionSeries(); 031 scriptURI = cc.getScriptURI(); 032 maybeUgly = MediaWiki.MAYBE_UGLY_URL_PATTERN.matcher( uri.toASCIIString() ).matches(); 033 } 034 035 036 037 /** Completes the construction of this PollwikiVS. Call once only. 038 */ 039 void init( final VoteServer vS ) throws IOException { cache = new WikiCache( vS ); } 040 // after vS.pollwiki assigned 041 042 043 044 // ------------------------------------------------------------------------------------ 045 046 047 /** Encodes a page name and appends it to the wiki {@linkplain #uri() base URL}. 048 * Usage example:<pre> 049 * 050 * String href = voteServer.pollwiki().encodePageSpecifier( 051 * "Main page" ).build().toASCIIString();</pre> 052 * 053 * @see #appendPageSpecifier(String) 054 */ 055 public UriBuilder encodePageSpecifier( final String pageName ) 056 { 057 return MediaWiki.encodePageSpecifier( UriBuilder.fromUri(uri), maybeUgly, pageName ); 058 } 059 060 061 062 /** The cache of data from this pollwiki's database. 063 */ 064 public WikiCache cache() { return cache; } 065 066 067 private WikiCache cache; // final after init 068 069 070 071 /** The password for the vote-server's wiki accounts, which go by the username Vobot. 072 * These accounts must be created in the pollwiki associated with the vote-server and 073 * in every remote wiki to which users patch differences or otherwise edit texts via 074 * the services of the vote-server. 075 * 076 * @see ConstructionContext#setPassword(String) 077 */ 078 public String password() { return password; } 079 080 081 private final String password; 082 083 084 085 /** The identifier of the revision series, which is zero or greater. The value is 086 * incremented whenever the vote-server is associated with a new pollwiki, or 087 * whenever the current pollwiki's revision history is altered by reassigning its 088 * revision identifiers. 089 * 090 * @see ConstructionContext#setRevisionSeries(int) 091 */ 092 public int revisionSeries() { return revisionSeries; } 093 094 095 private final int revisionSeries; 096 097 098 099 /** The base location for script execution in the pollwiki, without a trailing slash 100 * (/). If the pollwiki has a domain name then that name should be used here in its 101 * fully qualified form, even if the wiki is hosted locally along with the 102 * vote-server. Do not use "localhost" or the short host name alone, otherwise the 103 * vote-server may be unable to login via the MediaWiki API (1.16.1.). For example 104 * <code>http://reluk.ca/mediawiki</code>. Script requests may be constructed by 105 * appending the script path and parameters:<pre 106 * 107 *> scriptURI().toASCIIString() + "/index.php?oldid=1138"</pre> 108 * 109 * @see ConstructionContext#setScriptLocation(String) 110 * @see ConstructionContext#setScriptURI(URI) 111 */ 112 public URI scriptURI() { return scriptURI; } // cf. votorola.a.web.gwt.App.getScriptLocation() 113 114 115 private final URI scriptURI; 116 117 118 119 /** The base location for requesting pages from the pollwiki. This is either the 120 * standard access location, such as "{@linkplain #scriptURI() scriptURI}/index.php" 121 * for example, or an alias without a trailing slash (/). In the latter case 122 * <code>$wgUsePathInfo</code> is assumed to be true. Page requests formed on this 123 * base should generally not be redirected by the wiki or relays of Crossforum 124 * Theatre state are likely to fail owing to a {@linkplain 125 * votorola.s.gwt.stage.ReferrerRelayer#KEY_HREF URL mismatch}. 126 * 127 * @see #appendPageSpecifier(String) 128 * @see #maybeUgly() 129 * @see ConstructionContext#setLocation(String) 130 * @see ConstructionContext#setURI(URI) 131 * @see <a href='http://www.mediawiki.org/wiki/Manual:$wgUsePathInfo' 132 * target='_top'>$wgUsePathInfo</a> 133 */ // per INLDOC 134 public URI uri() { return uri; } // cf. votorola.a.web.gwt.App.getLocation() 135 136 137 private final URI uri; 138 139 140 141 // - P o l l w i k i ------------------------------------------------------------------ 142 143 144 /** @see #uri() 145 * @see #encodePageSpecifier(String) 146 */ 147 public StringBuilder appendPageSpecifier( final String encodedPageName ) 148 { 149 return MediaWiki.appendPageSpecifier( new StringBuilder(uri.toASCIIString()), maybeUgly, 150 encodedPageName ); 151 } 152 153 154 155 /** @see #uri() 156 */ 157 public boolean maybeUgly() { return maybeUgly; } 158 159 160 private final boolean maybeUgly; 161 162 163 164 /** {@inheritDoc} This recognizer is based on a snapshot of the pollwiki's interface 165 * messages. Any change in the relevant messages (unusual) will go undetected by the 166 * recognizer until this PollwikiVS instance is recreated, e.g. by restarting the 167 * vote-server. 168 */ 169 public PipeRecognizer pipeRecognizer() 170 { 171 if( pipeRecognizer == null ) // non-atomic test/set may duplicate set, no harm 172 { 173 String pipePrefix = null; 174 String pipeSuffix = null; 175 final URI queryURI; 176 try{ queryURI = new URI( scriptURI() + 177 "/api.php?action=query&meta=allmessages&ammessages=Vo-pipePrefix%7CVo-pipeSuffix&format=xml" ); } 178 catch( URISyntaxException x ) { throw new RuntimeException( x ); } 179 180 logger.fine( "querying pollwiki for pipe-related interface messages: " + queryURI ); 181 final Spool spool = new Spool1(); 182 read: try 183 { 184 final URLConnection http = queryURI.toURL().openConnection(); // not actually open yet 185 final XMLStreamReader xml = MediaWiki.requestXML( http, spool ); 186 while( xml.hasNext() ) 187 { 188 xml.next(); 189 if( !xml.isStartElement() ) continue; 190 191 if( "allmessages".equals( xml.getLocalName() )) 192 { 193 while( xml.hasNext() ) 194 { 195 xml.next(); 196 if( xml.isEndElement() 197 && "allmessages".equals( xml.getLocalName() )) break read; 198 199 if( !xml.isStartElement() ) continue; 200 201 if( !"message".equals( xml.getLocalName() )) continue; 202 203 if( xml.getAttributeValue(/*ns*/null,"missing") != null ) continue; 204 205 final String name = xml.getAttributeValue( /*ns*/null,"name" ); 206 final String value = xml.getElementText(); // moves state to </message> 207 if( "Vo-pipePrefix".equals( name )) pipePrefix = value; 208 else if( "Vo-pipeSuffix".equals( name )) pipeSuffix = value; 209 } 210 } 211 MediaWiki.test_error( xml ); 212 } 213 throw new MediaWiki.MalformedResponse( "missing 'allmessages' element" ); 214 } 215 catch( IOException|XMLStreamException x ) 216 { 217 logger.log( LoggerX.WARNING, "unable to obtain pipe configuration", x ); 218 } 219 finally{ spool.unwind(); } 220 if( pipePrefix == null || pipeSuffix == null ) pipeRecognizer = new PipeRecognizer0(); 221 else pipeRecognizer = new PipeRecognizer1( pipePrefix, pipeSuffix ); 222 } 223 return pipeRecognizer; 224 } 225 226 227 private volatile PipeRecognizer pipeRecognizer; /* lazy init to allow recovery in 228 case wiki is initially unreachable */ 229 230 231 232 public String positionPageName( final String personName, final String pollName ) 233 { 234 return PositionID.pageName( personName, pollName, pipeRecognizer() ); 235 } 236 237 238 239 // ==================================================================================== 240 241 242 /** A context for configuring the construction of a {@linkplain PollwikiVS PollwikiVS}. 243 */ 244 public static @ThreadSafe final class ConstructionContext 245 { 246 247 248 /** Finalizes the configuration. 249 */ 250 void postConfigure( final VoteServer.ConstructionContext vsCC ) throws URISyntaxException 251 { 252 if( scriptURI == null ) 253 { 254 setScriptLocation( "http://" + vsCC.getServerName() + "/mediawiki" ); 255 } 256 if( uri == null ) setLocation( scriptURI.toASCIIString() + "/index.php" ); 257 } 258 259 260 261 // -------------------------------------------------------------------------------- 262 263 264 /** The base location for requesting wiki pages, or null for the default. 265 * 266 * @see PollwikiVS#uri() 267 * @see #setLocation(String) 268 * @see #setURI(URI) 269 */ 270 public URI getURI() { return uri; } 271 272 273 private URI uri = null; 274 275 276 /** Sets the base location for requesting wiki pages. The default value is 277 * null, which translates at runtime to "http://{@linkplain #getScriptURI() 278 * scriptURI}/index.php". 279 * 280 * @see PollwikiVS#uri() 281 * @throws IllegalArgumentException if the URI ends with a slash '/' character. 282 */ 283 @ThreadRestricted("constructor") 284 public void setLocation( final String s ) throws URISyntaxException 285 { 286 setURI( new URI( s )); 287 } 288 289 290 /** Sets the base location for requesting wiki pages. The default value is 291 * null, which translates at runtime to "http://{@linkplain #getScriptURI() 292 * scriptURI}/index.php". 293 * 294 * @see PollwikiVS#uri() 295 * @throws IllegalArgumentException if the URI ends with a slash '/' character. 296 */ 297 public @ThreadRestricted("constructor") void setURI( final URI u ) 298 { 299 if( u.toString().endsWith( "/" )) 300 { 301 throw new IllegalArgumentException( "URI ends with '/'" ); 302 } 303 304 uri = u; 305 } 306 307 308 309 /** The password for this vote-server's wiki account. 310 * 311 * @see PollwikiVS#password() 312 * @see #setPassword(String) 313 */ 314 public String getPassword() { return password; } 315 316 317 private String password = "insecure password"; 318 319 320 /** Sets the password for this vote-server's wiki account, which goes by the 321 * username Vobot. The default value is "insecure password". 322 * 323 * @see PollwikiVS#password() 324 */ 325 @ThreadRestricted("constructor") 326 public void setPassword( final String p ) { password = p; } 327 328 329 330 /** The identifier of the revision series. 331 * 332 * @see PollwikiVS#revisionSeries() 333 * @see #setRevisionSeries(int) 334 */ 335 public int getRevisionSeries() { return revisionSeries; } 336 337 338 private int revisionSeries; 339 340 341 /** Sets the identifier of the revision series. The default value is zero. 342 * If you change it because of a change of pollwiki, then you should also 343 * clear the {@linkplain WikiCache wiki cache}. 344 * 345 * @see PollwikiVS#revisionSeries() 346 */ 347 @ThreadRestricted("constructor") 348 public void setRevisionSeries( final int p ) { revisionSeries = p; } 349 350 351 352 /** The base URI for requesting wiki scripts, or null for the default. 353 * 354 * @see PollwikiVS#scriptURI() 355 * @see #setScriptLocation(String) 356 * @see #setScriptURI(URI) 357 */ 358 public URI getScriptURI() { return scriptURI; } 359 360 361 private URI scriptURI = null; 362 363 364 /** Sets the base URI for requesting wiki scripts. The default value is null, 365 * which translates at runtime to "http://{@linkplain VoteServer#serverName() 366 * serverName}/mediawiki". 367 * 368 * @see PollwikiVS#scriptURI() 369 * @throws IllegalArgumentException if the URI ends with a slash '/' character. 370 */ 371 @ThreadRestricted("constructor") 372 public void setScriptLocation( final String s ) throws URISyntaxException 373 { 374 setScriptURI( new URI( s )); 375 } 376 377 378 /** Sets the base URI for requesting wiki scripts. The default value is null, 379 * which translates at runtime to "http://{@linkplain VoteServer#serverName() 380 * serverName}/mediawiki". 381 * 382 * @see PollwikiVS#scriptURI() 383 * @throws IllegalArgumentException if the URI ends with a slash '/' character. 384 */ 385 public @ThreadRestricted("constructor") void setScriptURI( final URI u ) 386 { 387 if( u.toString().endsWith( "/" )) 388 { 389 throw new IllegalArgumentException( "URI ends with '/'" ); 390 } 391 392 scriptURI = u; 393 } 394 395 396 } 397 398 399 400//// P r i v a t e /////////////////////////////////////////////////////////////////////// 401 402 403 private static final Logger logger = LoggerX.i( PollwikiVS.class ); 404 405 406}