package 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. import java.io.*; import java.net.*; import java.util.logging.*; import votorola.g.logging.*; import javax.ws.rs.core.UriBuilder; import javax.xml.stream.*; import votorola.a.position.*; import votorola.g.*; import votorola.g.hold.*; import votorola.g.lang.*; /** A pollwiki from the viewpoint of a vote-server. * * @see VoteServer#pollwiki() * @see ../s/manual.xht#wiki */ public @ThreadSafe final class PollwikiVS implements Pollwiki { /** Partially constructs a PollwikiVS for {@linkplain #init(VoteServer) init} to finish. */ PollwikiVS( final ConstructionContext cc ) throws URISyntaxException { uri = cc.getURI(); password = cc.getPassword(); revisionSeries = cc.getRevisionSeries(); scriptURI = cc.getScriptURI(); maybeUgly = MediaWiki.MAYBE_UGLY_URL_PATTERN.matcher( uri.toASCIIString() ).matches(); } /** Completes the construction of this PollwikiVS. Call once only. */ void init( final VoteServer vS ) throws IOException { cache = new WikiCache( vS ); } // after vS.pollwiki assigned // ------------------------------------------------------------------------------------ /** Encodes a page name and appends it to the wiki {@linkplain #uri() base URL}. * Usage example:
      *
      *     String href = voteServer.pollwiki().encodePageSpecifier(
      *       "Main page" ).build().toASCIIString();
* * @see #appendPageSpecifier(String) */ public UriBuilder encodePageSpecifier( final String pageName ) { return MediaWiki.encodePageSpecifier( UriBuilder.fromUri(uri), maybeUgly, pageName ); } /** The cache of data from this pollwiki's database. */ public WikiCache cache() { return cache; } private WikiCache cache; // final after init /** The password for the vote-server's wiki accounts, which go by the username Vobot. * These accounts must be created in the pollwiki associated with the vote-server and * in every remote wiki to which users patch differences or otherwise edit texts via * the services of the vote-server. * * @see ConstructionContext#setPassword(String) */ public String password() { return password; } private final String password; /** The identifier of the revision series, which is zero or greater. The value is * incremented whenever the vote-server is associated with a new pollwiki, or * whenever the current pollwiki's revision history is altered by reassigning its * revision identifiers. * * @see ConstructionContext#setRevisionSeries(int) */ public int revisionSeries() { return revisionSeries; } private final int revisionSeries; /** The base location for script execution in the pollwiki, without a trailing slash * (/). If the pollwiki has a domain name then that name should be used here in its * fully qualified form, even if the wiki is hosted locally along with the * vote-server. Do not use "localhost" or the short host name alone, otherwise the * vote-server may be unable to login via the MediaWiki API (1.16.1.). For example * http://reluk.ca/mediawiki. Script requests may be constructed by * appending the script path and parameters:
    scriptURI().toASCIIString() + "/index.php?oldid=1138"
* * @see ConstructionContext#setScriptLocation(String) * @see ConstructionContext#setScriptURI(URI) */ public URI scriptURI() { return scriptURI; } // cf. votorola.a.web.gwt.App.getScriptLocation() private final URI scriptURI; /** The base location for requesting pages from the pollwiki. This is either the * standard access location, such as "{@linkplain #scriptURI() scriptURI}/index.php" * for example, or an alias without a trailing slash (/). In the latter case * $wgUsePathInfo is assumed to be true. Page requests formed on this * base should generally not be redirected by the wiki or relays of Crossforum * Theatre state are likely to fail owing to a {@linkplain * votorola.s.gwt.stage.ReferrerRelayer#KEY_HREF URL mismatch}. * * @see #appendPageSpecifier(String) * @see #maybeUgly() * @see ConstructionContext#setLocation(String) * @see ConstructionContext#setURI(URI) * @see $wgUsePathInfo */ // per INLDOC public URI uri() { return uri; } // cf. votorola.a.web.gwt.App.getLocation() private final URI uri; // - P o l l w i k i ------------------------------------------------------------------ /** @see #uri() * @see #encodePageSpecifier(String) */ public StringBuilder appendPageSpecifier( final String encodedPageName ) { return MediaWiki.appendPageSpecifier( new StringBuilder(uri.toASCIIString()), maybeUgly, encodedPageName ); } /** @see #uri() */ public boolean maybeUgly() { return maybeUgly; } private final boolean maybeUgly; /** {@inheritDoc} This recognizer is based on a snapshot of the pollwiki's interface * messages. Any change in the relevant messages (unusual) will go undetected by the * recognizer until this PollwikiVS instance is recreated, e.g. by restarting the * vote-server. */ public PipeRecognizer pipeRecognizer() { if( pipeRecognizer == null ) // non-atomic test/set may duplicate set, no harm { String pipePrefix = null; String pipeSuffix = null; final URI queryURI; try{ queryURI = new URI( scriptURI() + "/api.php?action=query&meta=allmessages&ammessages=Vo-pipePrefix%7CVo-pipeSuffix&format=xml" ); } catch( URISyntaxException x ) { throw new RuntimeException( x ); } logger.fine( "querying pollwiki for pipe-related interface messages: " + queryURI ); final Spool spool = new Spool1(); read: try { final URLConnection http = queryURI.toURL().openConnection(); // not actually open yet final XMLStreamReader xml = MediaWiki.requestXML( http, spool ); while( xml.hasNext() ) { xml.next(); if( !xml.isStartElement() ) continue; if( "allmessages".equals( xml.getLocalName() )) { while( xml.hasNext() ) { xml.next(); if( xml.isEndElement() && "allmessages".equals( xml.getLocalName() )) break read; if( !xml.isStartElement() ) continue; if( !"message".equals( xml.getLocalName() )) continue; if( xml.getAttributeValue(/*ns*/null,"missing") != null ) continue; final String name = xml.getAttributeValue( /*ns*/null,"name" ); final String value = xml.getElementText(); // moves state to if( "Vo-pipePrefix".equals( name )) pipePrefix = value; else if( "Vo-pipeSuffix".equals( name )) pipeSuffix = value; } } MediaWiki.test_error( xml ); } throw new MediaWiki.MalformedResponse( "missing 'allmessages' element" ); } catch( IOException|XMLStreamException x ) { logger.log( LoggerX.WARNING, "unable to obtain pipe configuration", x ); } finally{ spool.unwind(); } if( pipePrefix == null || pipeSuffix == null ) pipeRecognizer = new PipeRecognizer0(); else pipeRecognizer = new PipeRecognizer1( pipePrefix, pipeSuffix ); } return pipeRecognizer; } private volatile PipeRecognizer pipeRecognizer; /* lazy init to allow recovery in case wiki is initially unreachable */ public String positionPageName( final String personName, final String pollName ) { return PositionID.pageName( personName, pollName, pipeRecognizer() ); } // ==================================================================================== /** A context for configuring the construction of a {@linkplain PollwikiVS PollwikiVS}. */ public static @ThreadSafe final class ConstructionContext { /** Finalizes the configuration. */ void postConfigure( final VoteServer.ConstructionContext vsCC ) throws URISyntaxException { if( scriptURI == null ) { setScriptLocation( "http://" + vsCC.getServerName() + "/mediawiki" ); } if( uri == null ) setLocation( scriptURI.toASCIIString() + "/index.php" ); } // -------------------------------------------------------------------------------- /** The base location for requesting wiki pages, or null for the default. * * @see PollwikiVS#uri() * @see #setLocation(String) * @see #setURI(URI) */ public URI getURI() { return uri; } private URI uri = null; /** Sets the base location for requesting wiki pages. The default value is * null, which translates at runtime to "http://{@linkplain #getScriptURI() * scriptURI}/index.php". * * @see PollwikiVS#uri() * @throws IllegalArgumentException if the URI ends with a slash '/' character. */ @ThreadRestricted("constructor") public void setLocation( final String s ) throws URISyntaxException { setURI( new URI( s )); } /** Sets the base location for requesting wiki pages. The default value is * null, which translates at runtime to "http://{@linkplain #getScriptURI() * scriptURI}/index.php". * * @see PollwikiVS#uri() * @throws IllegalArgumentException if the URI ends with a slash '/' character. */ public @ThreadRestricted("constructor") void setURI( final URI u ) { if( u.toString().endsWith( "/" )) { throw new IllegalArgumentException( "URI ends with '/'" ); } uri = u; } /** The password for this vote-server's wiki account. * * @see PollwikiVS#password() * @see #setPassword(String) */ public String getPassword() { return password; } private String password = "insecure password"; /** Sets the password for this vote-server's wiki account, which goes by the * username Vobot. The default value is "insecure password". * * @see PollwikiVS#password() */ @ThreadRestricted("constructor") public void setPassword( final String p ) { password = p; } /** The identifier of the revision series. * * @see PollwikiVS#revisionSeries() * @see #setRevisionSeries(int) */ public int getRevisionSeries() { return revisionSeries; } private int revisionSeries; /** Sets the identifier of the revision series. The default value is zero. * If you change it because of a change of pollwiki, then you should also * clear the {@linkplain WikiCache wiki cache}. * * @see PollwikiVS#revisionSeries() */ @ThreadRestricted("constructor") public void setRevisionSeries( final int p ) { revisionSeries = p; } /** The base URI for requesting wiki scripts, or null for the default. * * @see PollwikiVS#scriptURI() * @see #setScriptLocation(String) * @see #setScriptURI(URI) */ public URI getScriptURI() { return scriptURI; } private URI scriptURI = null; /** Sets the base URI for requesting wiki scripts. The default value is null, * which translates at runtime to "http://{@linkplain VoteServer#serverName() * serverName}/mediawiki". * * @see PollwikiVS#scriptURI() * @throws IllegalArgumentException if the URI ends with a slash '/' character. */ @ThreadRestricted("constructor") public void setScriptLocation( final String s ) throws URISyntaxException { setScriptURI( new URI( s )); } /** Sets the base URI for requesting wiki scripts. The default value is null, * which translates at runtime to "http://{@linkplain VoteServer#serverName() * serverName}/mediawiki". * * @see PollwikiVS#scriptURI() * @throws IllegalArgumentException if the URI ends with a slash '/' character. */ public @ThreadRestricted("constructor") void setScriptURI( final URI u ) { if( u.toString().endsWith( "/" )) { throw new IllegalArgumentException( "URI ends with '/'" ); } scriptURI = u; } } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private static final Logger logger = LoggerX.i( PollwikiVS.class ); }