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.script.ScriptException; import org.apache.wicket.*; import org.apache.wicket.markup.html.*; import org.apache.wicket.markup.html.basic.*; import org.apache.wicket.request.cycle.*; 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.*; import votorola.g.lang.*; import votorola.g.locale.*; import votorola.g.web.wic.*; import votorola.g.util.*; import static votorola.a.voter.IDPair.NOBODY; /** A poll overview page. 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 * description of the poll and a reconstruction link. 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}.
reconstructWhether to construct the poll from scratch. A value of 'y' constructs the * poll from scratch by ignoring any cached configuration items, while 'n' * constructs it normally. Use 'y' after modifying the definition of the poll * (or one of dependencies) on the wiki side, in order to see the effect on the * server side immediately. Normally there would be a delay of indeterminate * duration.'n'
* * @see WP_Poll.html */ @ThreadRestricted("wicket") @org.apache.wicket.devutils.stateless.StatelessComponent public final class WP_Poll extends VPageHTML implements TabbedPage { /** Constructs a WP_Poll. */ public WP_Poll( final PageParameters pP ) throws IOException, ScriptException, SQLException // bookmarkable page iff constructor public & (default|PageParameter) { super( pP ); final VRequestCycle cycle = VRequestCycle.get(); final PollService poll; final StringWriter logStringWriter; { final String name = Poll.U.toName( maybeRedirect_P( WP_Poll.class, pP, cycle )); final String reconstruct = pP.get( "reconstruct" ).toString( "n" ); if( "n".equals( reconstruct )) { logStringWriter = null; poll = pollFor( name, cycle ); } else if( "y".equals( reconstruct )) { logStringWriter = new StringWriter(); final PrintWriter logWriter = new PrintWriter( logStringWriter ); poll = pollFor( name, cycle, logWriter ); } else { VSession.get().error( "improper value for page parameter 'reconstruct': " + reconstruct ); throw new RestartResponseException( new WP_Message() ); } } final VSession session = VSession.get(); final String pollName = poll.name(); session.scopePoll().setLastName( pollName ); // Write glue for the GWT stage module of Crossforum Theatre // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { final StringBuilder b = WC_Stage.appendLeader( session.user(), VOWicket.get().vsRun().voteServer(), cycle ); // s_gwt_stage_Stage_init, per WC_Stage below // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` b.append( "voGWTConfig.s_gwt_stage_Stage_init = function()" + "{" ); final IDPair actor = session.scopeVoterPage().getLastIDPair(); if( !NOBODY.equals( actor )) { b.append( "s_gwt_stage_Stage_setActorName( '" ); // consistent with WP_Votespace and Rank b.append( actor.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_Poll.this, cycle )); add( new WC_WGLogo( "wgLogo", poll.wgLogoImageLocation(), poll.wgLogoLinkTarget(), cycle )); { final String mapPageName = poll.divisionSmallMapPageName(); add( mapPageName == null? newNullComponent( "divisionSmallMap" ): new WC_DivisionSmallMap( "divisionSmallMap", poll.divisionPageName(), mapPageName, cycle )); final WC_NavPile navPile = new WC_NavPile( "navPile", navTab(cycle), cycle ); navPile.setRenderBodyOnly( false ); // for sake of 'id' assigned in WP_Poll.html add( navPile ); } // Title and summary description // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - final BundleFormatter bunW = cycle.bunW(); { final String title = bunW.l( "s.wic.count.WP_Poll.title" ); add( new Label( "title", title + " - " + pollName )); } add( new Label( "hName", poll.name() )); { final String displayTitle = poll.displayTitle(); if( displayTitle == null ) add( newNullComponent( "hDisplayTitle" )); else add( new Label( "hDisplayTitle", ": " + displayTitle )); } add( new Label( "summaryDescription", poll.summaryDescription() )); // Reconstruction // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { final PageParameters linkParameters = new PageParameters( pP ); linkParameters.set( "reconstruct", "y" ); final BookmarkablePageLinkX link = new BookmarkablePageLinkX( "reconstruct", WP_Poll.class, linkParameters ); link.setBody( bunW.l( "s.wic.count.WP_Poll.reconstruct" )); link.add( AttributeModifier.replace( "title", bunW.l( "s.wic.count.WP_Poll.reconstructTip" ))); add( link ); } if( logStringWriter == null ) add( newNullComponent( "log" )); else add( new Label( "log", logStringWriter.toString() )); } // ------------------------------------------------------------------------------------ /** Fetches the poll correponding to the 'p' parameter. If the 'p' parameter is * missing, it is set to the name of the {@linkplain Poll#TEST_POLL_NAME test poll}, * and a 303 redirect exception is thrown. * * @see #pollFor(String,VRequestCycle) */ static PollService ensurePoll( final Class pageClass, PageParameters pP, final VRequestCycle cycle ) throws IOException, ScriptException, SQLException { final String name = Poll.U.toName( maybeRedirect_P( pageClass, pP, cycle )); return pollFor( name, cycle ); } /** Effects a redirect in response to missing mandatory parameters. If the 'p' * parameter is missing, it is set to the name of the {@linkplain * Poll#TEST_POLL_NAME test poll} and a 303 (see other) redirect exception is * thrown. * *

This method may also do a 301 (permanent) redirect in response to obsolete * parameter values. Currently these include polls that had to be moved to new names * for technical reasons.

* * @return the value of the 'p' parameter. */ public static String maybeRedirect_P( final Class pageClass, final PageParameters pP, final VRequestCycle cycle ) { final String p = pP.get( "p" ).toString(); if( p == null ) { pP.set( "p", Poll.U.toQuery( Poll.TEST_POLL_NAME )); throw new RedirectException( cycle.uriFor(pageClass,pP).toASCIIString(), 303 ); } String area = null; if( p.equals( "BGE" )) area = "De"; else if( p.equals( "de" )) area = "G"; else if( p.equals( "edor" )) area = "G"; else if( p.equals( "grfin" )) area = "Tor"; else if( p.equals( "m" )) area = "Tor"; else if( p.equals( "MetaGov" )) area = "G"; else if( p.equals( "Piraten" )) area = "De"; else if( p.equals( "sandbox" )) area = "G"; else if( p.equals( "tsmp" )) area = "Tor"; else if( p.equals( "tsmpp" )) area = "Tor"; else if( p.equals( "vohall" )) area = "G"; else if( p.equals( "vop" )) area = "G"; else if( p.equals( "w19c" )) area = "Tor"; else if( p.equals( "w20c" )) area = "Tor"; else if( p.equals( "wpe" )) area = "G"; if( area != null ) { pP.set( "p", area + "!p!" + p ); throw new RedirectException( cycle.uriFor(pageClass,pP).toASCIIString(), 301 ); } return p; } /** Fetches the poll corresponding to the specified service name. * * @see #ensurePoll(Class,PageParameters,VRequestCycle) * @see #pollOrNullFor(PageParameters,VRequestCycle) * @see PollFetcher#poll() */ static PollService pollFor( final String name, final VRequestCycle cycle ) throws IOException, ScriptException, SQLException { return pollFor( name, cycle, /*logWriter*/null ); } private static PollService pollFor( final String name, final VRequestCycle cycle, final PrintWriter logWriter ) throws IOException, ScriptException, SQLException { final PollService.VoteServerScope.Run vsRunPS = VOWicket.get().vsRun().scopePoll(); try { return logWriter == null? vsRunPS.ensurePoll( name ): vsRunPS.constructCachedPoll( name, logWriter ); } catch( VoterService.IllegalNameException x ) { VSession.get().error( "Unable to fetch poll \"" + name + "\": " + x.toString() ); throw new RestartResponseException( new WP_Message() ); } } /** Fetches the poll corresponding to query parameter 'p', if it is specified. * * @return poll, or null if none is specified. * * @see #pollFor(String,VRequestCycle) */ static PollService pollOrNullFor( final PageParameters pP, final VRequestCycle cycle ) throws IOException, ScriptException, SQLException { final String p = pP.get( "p" ).toString(); return p == null? null: pollFor( Poll.U.toName(p), cycle ); } /** Returns the specified page parameters (pP) with a value for the poll parameter * ('p') as recalled from the session. If a value is recalled but pP is null, then * pP is automatically constructed. * * @param pP the parameter map, which may be null. */ static PageParameters withRecall_p( PageParameters pP ) { final String lastName = VSession.get().scopePoll().getLastName(); if( lastName != null ) pP = PageParametersX.withSet( pP, "p", Poll.U.toQuery(lastName) ); return pP; } // - 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 poll overview page, an instance of WP_Poll. */ static final NavTab NAV_TAB = new PollTab(); // ==================================================================================== /** A serializeable container for a poll and its latest count. */ static @ThreadRestricted("wicket") final class PollFetcher implements Serializable { private static final long serialVersionUID = 0L; /** Constructs a poll fetcher. */ PollFetcher( final PollService poll ) { pollName = poll.name(); pollOrNull = poll; } /** Constructs a poll fetcher. */ PollFetcher( final String pollName, final VRequestCycle cycle ) { this.pollName = pollName; poll( cycle ); // in anticipation, taking advantage of the cycle reference } private final String pollName; // -------------------------------------------------------------------------------- /** The latest count, or null if there is none. */ Count countToReport() { if( countOrNull == null && !"".equals(count_readyDirectoryPath) ) // for a newly constructed 'this', or deserialized one that had a count { try { countOrNull = poll().countToReportT(); } catch( Exception x ) { throw VotorolaRuntimeException.castOrWrapped( x ); } // IOx or SQLx, not much expected final String path; if( countOrNull == null ) path = ""; else path = countOrNull.readyDirectory().getPath(); if( count_readyDirectoryPath == null ) count_readyDirectoryPath = path; else if( !count_readyDirectoryPath.equals( path )) { throw new VotorolaRuntimeException( "The poll has been recounted since this page was constructed. FIX this to show the user a WP_Message, with a link to new page." ); } } if( countOrNull != null && !countOrNull.readyDirectory().isMounted() ) { throw new VotorolaRuntimeException( "The poll count has been unmounted since this page was constructed. FIX this to show the user a WP_Message, with a link to new page." ); } return countOrNull; } private transient Count countOrNull; private String count_readyDirectoryPath; /** The poll. */ PollService poll() { return poll( VRequestCycle.get() ); } PollService poll( final VRequestCycle cycle ) { if( pollOrNull == null && pollName != null ) { try { pollOrNull = pollFor( pollName, cycle ); // for a newly constructed or deserialized 'this' } catch( Exception x ) { throw VotorolaRuntimeException.castOrWrapped( x ); } // IOException, ScriptException, SQLException } return pollOrNull; } private transient PollService pollOrNull; } // ==================================================================================== private static @ThreadSafe final class PollTab extends NavTab { public @Override Bookmark bookmark() { return new Bookmark( WP_Poll.class, withRecall_p(null) ); } public @Override Class pageClass() { return WP_Poll.class; } public @Override String shortTitle( VRequestCycle cycle ) { return cycle.bunW().l( "s.wic.count.WP_Poll.tab.shortTitle" ); } } // ==================================================================================== /** Session scope for instances of WP_Poll. * * @see VSession#scopePoll() */ public static @ThreadSafe class SessionScope implements Serializable { private static final long serialVersionUID = 0L; /** Constructs a SessionScope. */ public SessionScope( VSession session ) { this.session = session; } private final VSession session; // -------------------------------------------------------------------------------- /** The last poll-name fore-navigated to, or null if there is none. * * @see #setLastName(String) */ public String getLastName() { return lastName; } // Maybe the only code that absolutely needs this is in the supertab page // WP_CountEngine, which currently isn't rendered. private volatile String lastName = null; /** Sets the last poll-name fore-navigated to. * * @see #getLastName() */ public void setLastName( final String newLastName ) { final String oldLastName = lastName; // snapshot copy, for atomic test/return if( ObjectX.nullEquals( newLastName, oldLastName )) return; lastName = newLastName; session.dirty(); // per Session API } // /** The last node fore-navigated to, or null. If the optional correction is // * enabled and the current page is divisional, then ensureLast() will first be // * called using the current page path; thus correcting for any back-navigation. // * // * @see #getLast() // */ // public DivisionalNode lastNodeDisplayed( final DivisionalStratum stratum, // cf. votorola.a.voter.WC_VoterNavigator.SessionScope.lastVoterEmailDisplayed // final boolean correctForCurrent, final VRequestCycle cycle ) // { // DivisionalPath lastPath = null; // if( correctForCurrent ) // { // final Page page = cycle.responsePage(); // if( page instanceof DivisionalPage ) // { // lastPath = ensureLast( ((DivisionalPage)page).divisionalPath() ); // } // } // if( lastPath == null ) lastPath = last; // // return stratum.getNodeOnPath( lastPath ); // } /// Ripped out of old divisional code. Saved here as an example of handling the back /// button, if ever needed again. } }