package votorola.s.wic.count; // Copyright 2008-2010, 2012-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.sql.SQLException; import java.util.*; import javax.xml.stream.XMLStreamException; import org.apache.wicket.*; import org.apache.wicket.markup.html.basic.*; import org.apache.wicket.markup.html.link.*; import org.apache.wicket.markup.html.panel.*; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.markup.repeater.data.*; import org.apache.wicket.model.*; import org.apache.wicket.request.mapper.parameter.PageParameters; import votorola.a.*; import votorola.a.count.*; import votorola.a.voter.*; import votorola.a.web.wic.*; import votorola.g.lang.*; import votorola.g.locale.*; import votorola.g.web.wic.*; import static votorola.a.voter.IDPair.NOBODY; /** A view of the tallied results for a poll. A live {@linkplain * votorola.s.gwt.stage.StageV stage view} of Crossforum Theatre tops the page. The bulk * of the page is static HTML, including a summary of turnout and a ranked list of * candidates. For example: * *
http://reluk.ca:8080/v/w/Rank?8&p=G!p!sandbox
* *

The particular poll is specified by query parameter 'p'. Query parameters for this * page are:

* * * * * * * * * * * * * * * * * * * * * * * * * * * * *
KeyValueDefault
pThe name of the poll. Slash characters (/) are technically not allowed here * and may therefore be encoded as exclamation marks (!).Null, resulting in a 303 (see other) redirect that fills in the name of * the {@linkplain Poll#TEST_POLL_NAME test poll}.
uThe {@linkplain IDPair#username() username} of a candidate. The list of * candidates is paged to ensure that the specified candidate is visible. If the * candidate is not listed, the list is paged to the very end. Incompatible with * parameter 'u'; specify one or the other.Null, specifying no particular candidate.
vThe {@linkplain IDPair#email() email address} of a candidate. The list of * candidates is paged to ensure that the specified candidate is visible. If the * candidate is not listed, the list is paged to the very end. Incompatible with * parameter 'u'; specify one or the other.Null, specifying no particular candidate.
* * @see WP_Rank.html */ @ThreadRestricted("wicket") public final class WP_Rank extends VPageHTML implements TabbedPage, VoterPage { /** Constructs a WP_Rank. */ public WP_Rank( final PageParameters pP ) // bookmarkable page iff constructor public & (default|PageParameter) { super( pP ); final VRequestCycle cycle = VRequestCycle.get(); final String pollName = Poll.U.toName( WP_Poll.maybeRedirect_P( WP_Rank.class, pP, cycle )); pFet = new WP_Poll.PollFetcher( pollName, cycle ); final VSession session = VSession.get(); session.scopePoll().setLastName( pollName ); voterIDPair = VoterPage.U.idPairOrNobodyFor( pP ); session.scopeVoterPage().setLastIDPair( voterIDPair ); // Write glue for the GWT stage module of Crossforum Theatre // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - final VoteServer vS = VOWicket.get().vsRun().voteServer(); { final StringBuilder b = WC_Stage.appendLeader( session.user(), vS, cycle ); // s_gwt_stage_Stage_init, per WC_Stage below // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` b.append( "voGWTConfig.s_gwt_stage_Stage_init = function()" + "{" ); if( !NOBODY.equals( voterIDPair )) { b.append( "s_gwt_stage_Stage_setActorName( '" ); // rather than setDefaultActorName, per WP_Votespace b.append( voterIDPair.username() ).append( "' );" ); } b.append( "s_gwt_stage_Stage_setDefaultPollName( '" ); b.append( pollName ).append( "' );" + "};" ); // ` ` ` add( new WC_Stage( "stage", "votorola.s.gwt.wic.CountIn", b, cycle )); } // Render view // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - add( new WC_NavigationHead( "navHead", WP_Rank.this, cycle )); add( new WC_WGLogo( "wgLogo", pFet.poll().wgLogoImageLocation(), pFet.poll().wgLogoLinkTarget(), cycle )); { final String mapPageName = pFet.poll().divisionSmallMapPageName(); add( mapPageName == null? newNullComponent( "divisionSmallMap" ): new WC_DivisionSmallMap( "divisionSmallMap", pFet.poll().divisionPageName(), mapPageName, cycle )); } add( new WC_NavPile( "navPile", navTab(cycle), cycle )); init_content( vS, cycle ); } private void init_content( final VoteServer vS, final VRequestCycle cycle ) { final BundleFormatter bunW = cycle.bunW(); // Title // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - final Model titleModel = new Model( bunW.l( "s.wic.count.WP_Rank.title" )); add( new Label( "title", titleModel )); if( pFet.poll() == null ) { assert false: "poll is never null"; // FIX clean up add( newNullComponent( "content" )); return; } titleModel.setObject( titleModel.getObject() + " - " + pFet.poll().name() ); final Fragment content = new Fragment( "content", "singlefrag-content", WP_Rank.this ); add( content ); content.add( new Label( "hName", pFet.poll().name() )); { final String displayTitle = pFet.poll().displayTitle(); if( displayTitle == null ) content.add( newNullComponent( "hDisplayTitle" )); else content.add( new Label( "hDisplayTitle", ": " + displayTitle )); } // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = final BundleFormatter bunA = cycle.bunA(); final Fragment main; if( pFet.countToReport() == null ) { main = new Fragment( "main-content", "frag-main-content-no-count", WP_Rank.this ); main.add( new Label( "explanation", bunA.l( "a.count.noResultsToReport" ))); content.add( main ); return; } main = new Fragment( "main-content", "singlefrag-main-content-normal", WP_Rank.this ); content.add( main ); // Summary table // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { final long populationSize = pFet.poll().populationSize(); main.add( new Label( "populationSize-index", bunA.l( "a.count.Count.summaryData.populationSize-index" ))); main.add( new Label( "populationSize-datum", populationSize > 0? bunA.format( "%,d", populationSize ): bunA.l( "a.quantityUnknown" ))); main.add( new Label( "populationSize-note", pFet.poll().populationSizeExplanation() )); main.add( new Label( "castVolume-index", bunA.l( "a.count.Count.summaryData.singleCastCount-index" ))); main.add( new Label( "castVolume-datum", bunA.format( "%,d", pFet.countToReport().castVolume() ))); main.add( new Label( "castVolume-note", bunA.l( "a.count.Count.summaryData.singleCastCount-note" ))); main.add( new Label( "turnoutPercent-index", bunA.l( "a.count.Count.summaryData.turnoutPercent-index" ))); main.add( new Label( "turnoutPercent-datum", populationSize > 0? bunA.format( "%.3f", pFet.countToReport().castVolume() * 100d / populationSize ): bunA.l( "a.quantityUnknown" ))); main.add( new Label( "turnoutPercent-note", bunA.l( "a.count.Count.summaryData.turnoutPercent-note" ))); } // Rank table // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - main.add( new Label( "rankHeader-rank", bunA.l( "a.count.Count.rankHeader-rank" ))); main.add( new Label( "rankHeader-voterEmail", bunA.l( "a.count.Count.rankHeader-voterEmail" ))); main.add( new Label( "rankHeader-receiveVolume", bunA.l( "a.count.Count.rankHeader-receiveCount" ))); main.add( new Label( "rankHeader-receivePercent", bunA.l( "a.count.Count.rankHeader-receivePercent" ))); main.add( new Label( "note-receive", bunA.l( "a.count.Count.note-receive", pFet.countToReport().castVolume() ))); final RankDataProvider dataProvider = new RankDataProvider(); final int preScrollIndex; // -1 for none; -2 for none, and scroll to last page try { int index = -1; // till proven otherwise if( !NOBODY.equalsEmail( voterIDPair )) { index = -2; // till proven otherwise final CountNodeW node = pFet.countToReport().countTablePV().get( voterIDPair.email() ); if( node != null && node.getRankIndex() < dataProvider.size() ) { assert MAX_RANK_INDEX <= (long)Integer.MAX_VALUE: "safe to convert rank index to int"; index = (int)node.getRankIndex(); } titleModel.setObject( titleModel.getObject() + "/" + voterIDPair.username() ); } preScrollIndex = index; } catch( SQLException|XMLStreamException x ) { throw new RuntimeException( x ); } final DataView dataView = new DataView( "rankData", dataProvider ) { public @Override void populateItem( final Item item ) { final BundleFormatter bunA = VRequestCycle.get().bunA(); final CountNodeW node = item.getModelObject(); item.add( new Label( "rankData-rank", bunA.format( "%,d", node.getRank() ))); final PageParameters pP = new PageParameters( getPageParameters() ); pP.remove( "v" ); final String nodeEmail = node.email(); final String nodeUsername = node.person().username(); if( nodeEmail.equals( voterIDPair.email() )) // already selected { pP.remove( "u" ); // de-select appendStyleClass( item, "voterHighlight" ); } else pP.set( "u", nodeUsername ); // select final BookmarkablePageLinkX td = new BookmarkablePageLinkX( // JavaScript link "voterEmail", WP_Rank.class, pP ); item.add( td ); final BookmarkablePageLinkX link = new BookmarkablePageLinkX( // ordinary link, nested in JavaScript link "a", WP_Rank.class, pP ); link.setBody( nodeUsername ); td.add( link ); item.add( new Label( "rankData-receiveVolume", bunA.format( "%,d", node.receiveVolume() ))); item.add( new Label( "rankData-receivePercent", bunA.format( "%.2f", node.receiveVolume() * 100d / pFet.countToReport().castVolume() ))); } }; main.add( dataView ); final int itemsPerPage = 10; dataView.setItemsPerPage( itemsPerPage ); if( preScrollIndex == -2 ) { final int nP = dataView.getPageCount(); if( nP > 0 ) dataView.setCurrentPage( nP - 1 ); } else if( preScrollIndex > 0 ) dataView.setCurrentPage( preScrollIndex / itemsPerPage ); main.add( new Label( "rank-pageNav-prefix", bunW.l( "a.pageNav-prefix" ))); main.add( newPagingNavigator( "rank-pageNav", dataView )); main.add( new Label( "rank-pageNav-suffix", bunW.l( "a.pageNav-suffix", dataView.getPageCount() ))); // Count identifier // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - final ReadyDirectory ready = pFet.countToReport().readyDirectory(); final File snap = ready.snapDirectory(); main.add( new ExternalLink( "countID", /*href*/vS.votorolaURI().toASCIIString() + "/out/vocount/" + snap.getName() + "/" + ready.getName() + "/", /*body*/bunA.l( "a.OutputStore.setNominalDate", OutputStore.setNominalDate(new GregorianCalendar(),snap) ))); } // ------------------------------------------------------------------------------------ /** The maximum index viewable in the table of ranked candidates. */ public static final int MAX_RANK_INDEX = Integer.MAX_VALUE; // - T a b b e d - P a g e ------------------------------------------------------------ /** @see #NAV_TAB */ public NavTab navTab( VRequestCycle cycle ) { return NAV_TAB; } /** The navigation tab that fetches the ranking page, an instance of WP_Rank. */ static final NavTab NAV_TAB = new WP_Votespace.VotespaceTab( WP_Rank.class ) { public @Override String shortTitle( VRequestCycle cycle ) { return cycle.bunW().l( "s.wic.count.WP_Rank.tab.shortTitle" ); } }; // - V o t e r - P a g e -------------------------------------------------------------- public String voterEmail() { return voterIDPair.email(); } public IDPair voterIDPair() { return voterIDPair; } private final IDPair voterIDPair; public String voterUsername() { return voterIDPair.username(); } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private final WP_Poll.PollFetcher pFet; // ==================================================================================== private final class RankDataProvider implements IDataProvider { // - I - D a t a - P r o v i d e r ------------------------------------------------ public Iterator iterator( final int first, final int count ) { try { return pFet.countToReport().countTablePV().listByRankIndeces( first, first + count ).iterator(); } catch( SQLException|XMLStreamException x ) { throw new RuntimeException( x ); } } public IModel model( final CountNodeW node ) { return new Model( node ); } public int size() { return size; } private final int size; { final long s = pFet.countToReport().candidateCount(); // if( s > MAX_RANK_INDEX ) throw new VotorolaRuntimeException( "too many voters" ); /// no harm in silently displaying a trucated view size = s > MAX_RANK_INDEX? MAX_RANK_INDEX: (int)s; } // - I - D e t a c h a b l e ------------------------------------------------------ /** Does nothing. */ public void detach() {} } }