package votorola.s.wap; // Copyright 2011-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 com.google.gson.stream.*;
import java.io.*;
import java.nio.charset.*;
import java.util.*;
import java.util.regex.*;
import javax.mail.internet.*;
import javax.servlet.http.*;
import votorola.a.*;
import votorola.a.count.*;
import votorola.a.diff.*;
import votorola.a.position.*;
import votorola.a.voter.*;
import votorola.a.web.wap.*;
import votorola.g.*;
import votorola.g.io.*;
import votorola.g.lang.*;
import votorola.g.script.*;
import votorola.g.web.*;
import static votorola.a.voter.IDPair.NOBODY;
/** A web API for the {@linkplain DiffCache difference cache}. Calls are conventionally
* prefixed by 'd' (wCall=dDiff
). If you choose another prefix, then adjust
* the parameter names below accordingly. An example request is:
*
*
http://reluk.ca:8080/v/wap?wCall=dDiff&dAnchor=Frank-FlippityNet&dA=Georgina-BeenaCom(Mike-ZeleaCom&dB=Test-a-ZeleaCom&wPretty
*
* Specify which difference (or differences) to return using one or more of the * following parameters.
* *Key | *Value | *
---|---|
dA | * *Specifies the differences between the latest revisions of the anchor * (draft b) and each of the named authors (draft a). Authors are named by * mailish username. Multiple names are separated by left parentheses '(', as * for example 'dA=NAME1(NAME2(NAME3'. Depends on 'dAnchor' and 'dPoll'. | * *
dB | * *Specifies the differences between the latest revisions of the anchor * (draft a) and each of the named authors (draft b). Authors are named by * mailish username. Multiple names are separated by left parentheses '(', as * for example 'dB=NAME1(NAME2(NAME3'. Depends on 'dAnchor' and 'dPoll'. | * *
These parameters are specific to the difference cache API. See also the general * {@linkplain WAP WAP} parameters.
* *Key | *Value | *Default | *
---|---|---|
dAnchor | * *The anchor specified by mailish username. The anchor is the refererence * author for relative difference specifiers such as 'dA' and 'dB'. | * *Null, optional item. | * *
dPoll | * *The poll of the anchor draft specified by {@linkplain Poll#name() poll * name}. | * *{@value votorola.a.count.Poll#TEST_POLL_NAME} | * *
dPairData | * *Specify 'dPairData' or 'dPairData=y' to include additional data that * depends on fetching and constructing the {@linkplain DraftPair draft pair} for * each difference record. Such fetches are required in any case for 'dA' and * 'dB' differences, so this merely controls whether the data is included in the * response. | * *'n' | *
The response includes the following components. These are shown in JSON format * with explanatory comments:
{ * "d": { // or other prefix, per {@linkplain WAP wCall} query parameter * * "error": [ // only if a client-actionable error was detected. * * "MESSAGE", * "MESSAGE" * // and so on * ], * * "diff": { * * // Difference records each indexed by {@linkplain DiffKey difference key}. * * "KEY": { * * // Difference record. * * "aUserMnemonic": "USER MNEMONIC", * // A short abbreviation of the username of the first draft's author. * // Depends on query parameter 'dPairData'. * "aUsername": "USERNAME", * // The mailish username of the first draft's author. Depends on query * // parameter 'dPairData'. * * "bUserMnemonic": "USER MNEMONIC", * // A short abbreviation of the username of the second draft's author. * // Depends on query parameter 'dPairData'. * "bUsername": "USERNAME", * // The mailish username of the second draft's author. Depends on query * // parameter 'dPairData'. * * "text": "TEXT" * // Text of 'diff' output. * } * // and so on, for each difference record * }, * * "diffX": { * * // Index into difference records. Only entries specifically requested are * // included here. * * "A": [ * * // Results for requested 'dA' differences in the order requested. Each * // entry is either a difference key, or null if the difference is unknown. * * "KEY", * null, * "KEY" * // and so on * ], * "B": [ * * // Results for requested 'dB' differences in the order requested. Each * // entry is either a difference key, or null if the difference is unknown. * * "KEY", * null, * "KEY" * // and so on * ] * }, * * "anchorMnemonic": "USER MNEMONIC", * // A short abbreviation of the anchor's username. Depends on query * // parameters 'dAnchor' and 'dPairData'. * } * }* *
For a baseline reference, here is a minimal pretty request together with the
* corresponding response:
* http://reluk.ca:8080/v/wap?wCall=dDiff&wPretty
{ * "d": { * "diff": {}, * "diffX": {} * } * }* *
Responses are intended to be {@linkplain Uncached uncached} when requesting * relative differences such as dA or dB.
*/ public final @ThreadRestricted("constructor") class DiffWAP extends Call { /** Constructs a DiffWAP. * * @see #prefix() * @see #req() */ public DiffWAP( final String prefix, final Requesting req, final ResponseConfiguration resConfig ) throws HTTPRequestException { super( prefix, req, resConfig ); final HttpServletRequest reqHS = req.request(); boolean isCacheable = true; // till proven otherwise toAddPairData = HTTPServletRequestX.getBooleanParameter( "dPairData", reqHS ); final HashMapwCall=dDiff
.
*/
public static final String CALL_TYPE = "Diff";
/** The pattern of a single mailish username in a list-form query value, such as
* "Jack-ThisOrg(Jill-ThatNet(Up HillCom". Use matcher.{@linkplain Matcher#find()
* find}() to scan the list; it will set group 1 to each username in turn.
*/
static final Pattern USERNAME_QUERY_ITEM_PATTERN = Pattern.compile(
"([^ ()][^()]*[^ ()])(?:[ ()]*[()][ ()]*|$)" );
// USERNAME SEPARATOR
//
// '(' is the item separator. It was chosen because it is illegal (or at least not
// recommended) in email addresses and requires no encoding in URLs. It is a weird
// separator so the matching is somewhat lenient here (though clients should not count
// on this). Each separator may actually consist of any combination of one or more
// left parentheses '(' and/or right parentheses ')' mixed with any number of space
// characters.
//
// http://tools.ietf.org/html/rfc822
// http://tools.ietf.org/html/rfc1035#section-2.3.1
// http://tools.ietf.org/html/rfc2396#section-2.3
// - C a l l --------------------------------------------------------------------------
public void respond( final Responding res ) throws IOException
{
final JsonWriter out = res.outJSON();
out.name( prefix() ).beginObject();
// error
// ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `
if( errorList != null )
{
out.name( "error" ).beginArray();
for( final Throwable t: errorList ) out.value( t.toString() );
out.endArray();
}
// diff
// ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `
out.name( "diff" ).beginObject();
final DiffCache diffCache = res.wap().vsRun().voteServer().diffCache();
final Writer outBuf = res.outBuf();
final JSONStringWriter outString = new JSONStringWriter( outBuf );
// "need not be closed"
final boolean wPretty = res.wPretty();
final String indent = res.outJSONIndent();
for( final DraftPair pair: pairSet )
{
final String key = pair.diffKeyParse().key();
final File diffFile = diffCache.diffFile( pair );
out.name( key ).beginObject();
// aUserMnemonic, aUsername
// ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `
if( toAddPairData )
{
out.name( "aUserMnemonic" ).value( pair.aUserMnemonic() );
out.name( "aUsername" ).value( pair.aCore().person().username() );
}
// bUserMnemonic, bUsername
// ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `
if( toAddPairData )
{
out.name( "bUserMnemonic" ).value( pair.bUserMnemonic() );
out.name( "bUsername" ).value( pair.bCore().person().username() );
}
// text
// ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `
out.flush();
outBuf.append( "," );
if( wPretty )
{
outBuf.append( '\n' );
int i = 0;
do { outBuf.append( indent ); ++i; } while( i < 4 );
}
outBuf.append( "\"text\":" ); if( wPretty ) outBuf.append( ' ' );
outBuf.append( '"' );
{
FileX.appendTo( outString, diffFile, Charset.defaultCharset() );
// outString.flush(); // redundant as it "need not be flushed"
}
outBuf.append( '"' );
out.endObject();
}
out.endObject();
// diffX
// ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `
out.name( "diffX" ).beginObject();
for( final Map.Entry