/*
* Copyright (c) 2001-2004 Ant-Contrib project. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.antcontrib.net;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.rmi.server.UID;
import java.util.*;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.TaskContainer;
/**
* This task does an http post. Name/value pairs for the post can be set in
* either or both of two ways, by nested Prop elements and/or by a file
* containing properties. Nested Prop elements are automatically configured by
* Ant. Properties from a file are configured by code borrowed from Property so
* all Ant property constructs (like ${somename}) are resolved prior to the
* post. This means that a file can be set up in advance of running the build
* and the appropriate property values will be filled in at run time.
*
* @author Dale Anson, danson@germane-software.com
* @version $Revision: 1.11 $
*/
public class PostTask extends Task {
/** Storage for name/value pairs to send. */
private Hashtable props = new Hashtable();
/** URL to send the name/value pairs to. */
private URL to = null;
/** File to read name/value pairs from. */
private File propsFile = null;
/** storage for Ant properties */
private String textProps = null;
/** encoding to use for the name/value pairs */
private String encoding = "UTF-8";
/** where to store the server response */
private File log = null;
/** append to the log? */
private boolean append = true;
/** verbose? */
private boolean verbose = true;
/** want to keep the server response? */
private boolean wantResponse = true;
/** store output in a property */
private String property = null;
/** how long to wait for a response from the server */
private long maxwait = 180000; // units for maxwait is milliseconds
/** fail on error? */
private boolean failOnError = false;
// storage for cookies
private static Hashtable cookieStorage = new Hashtable();
/** connection to the server */
private URLConnection connection = null;
/** for thread handling */
private Thread currentRunner = null;
/**
* Set the url to post to. Required.
*
* @param name the url to post to.
*/
public void setTo( URL name ) {
to = name;
}
/**
* Set the name of a file to read a set of properties from.
*
* @param f the file
*/
public void setFile( File f ) {
propsFile = f;
}
/**
* Set the name of a file to save the response to. Optional. Ignored if
* "want response" is false.
*
* @param f the file
*/
public void setLogfile( File f ) {
log = f;
}
/**
* Should the log file be appended to or overwritten? Default is true,
* append to the file.
*
* @param b append or not
*/
public void setAppend( boolean b ) {
append = b;
}
/**
* If true, progress messages and returned data from the post will be
* displayed. Default is true.
*
* @param b true = verbose
*/
public void setVerbose( boolean b ) {
verbose = b;
}
/**
* Default is true, get the response from the post. Can be set to false for
* "fire and forget" messages.
*
* @param b print/log server response
*/
public void setWantresponse( boolean b ) {
wantResponse = b;
}
/**
* Set the name of a property to save the response to. Optional. Ignored if
* "wantResponse" is false.
* @param name the name to use for the property
*/
public void setProperty( String name ) {
property = name;
}
/**
* Sets the encoding of the outgoing properties, default is UTF-8.
*
* @param encoding The new encoding value
*/
public void setEncoding( String encoding ) {
this.encoding = encoding;
}
/**
* How long to wait on the remote server. As a post is generally a two part
* process (sending and receiving), maxwait is applied separately to each
* part, that is, if 180 is passed as the wait parameter, this task will
* spend at most 3 minutes to connect to the remote server and at most
* another 3 minutes waiting on a response after the post has been sent.
* This means that the wait period could total as much as 6 minutes (or 360
* seconds). <p>
*
* The default wait period is 3 minutes (180 seconds).
*
* @param wait time to wait in seconds, set to 0 to wait forever.
*/
public void setMaxwait( int wait ) {
maxwait = wait * 1000;
}
/**
* Should the build fail if the post fails?
*
* @param fail true = fail the build, default is false
*/
public void setFailonerror( boolean fail ) {
failOnError = fail;
}
/**
* Adds a name/value pair to post. Optional.
*
* @param p A property pair to send as part of the post.
* @exception BuildException When name and/or value are missing.
*/
public void addConfiguredProp( Prop p ) throws BuildException {
String name = p.getName();
if ( name == null ) {
throw new BuildException( "name is null", getLocation() );
}
String value = p.getValue();
if ( value == null ) {
value = getProject().getProperty( name );
}
if ( value == null ) {
throw new BuildException( "value is null", getLocation() );
}
props.put( name, value );
}
/**
* Adds a feature to the Text attribute of the PostTask object
*
* @param text The feature to be added to the Text attribute
*/
public void addText( String text ) {
textProps = text;
}
/**
* Do the post.
*
* @exception BuildException On any error.
*/
public void execute() throws BuildException {
if ( to == null ) {
throw new BuildException( "'to' attribute is required", getLocation() );
}
final String content = getContent();
try {
if ( verbose )
log( "Opening connection for post to " + to.toString() + "..." );
// do the POST
Thread runner =
new Thread() {
public void run() {
DataOutputStream out = null;
try {
// set the url connection properties
connection = to.openConnection();
connection.setDoInput( true );
connection.setDoOutput( true );
connection.setUseCaches( false );
connection.setRequestProperty(
"Content-Type",
"application/x-www-form-urlencoded" );
// check if there are cookies to be included
for ( Iterator it = cookieStorage.keySet().iterator(); it.hasNext(); ) {
StringBuffer sb = new StringBuffer();
Object name = it.next();
if ( name != null ) {
String key = name.toString();
Cookie cookie = ( Cookie ) cookieStorage.get( name );
if ( to.getPath().startsWith( cookie.getPath() ) ) {
connection.addRequestProperty( "Cookie", cookie.toString() );
}
}
}
// do the post
if ( verbose ) {
log( "Connected, sending data..." );
}
out = new DataOutputStream( connection.getOutputStream() );
if ( verbose ) {
log( content );
}
out.writeBytes( content );
out.flush();
if ( verbose ) {
log( "Data sent." );
}
}
catch ( Exception e ) {
if ( failOnError ) {
throw new BuildException( e, getLocation() );
}
}
finally {
try {
out.close();
}
catch ( Exception e ) {
// ignored
}
}
}
}
;
runner.start();
runner.join( maxwait );
if ( runner.isAlive() ) {
runner.interrupt();
if ( failOnError ) {
throw new BuildException( "maxwait exceeded, unable to send data", getLocation() );
}
return ;
}
// read the response, if any, optionally writing it to a file
if ( wantResponse ) {
if ( verbose ) {
log( "Waiting for response..." );
}
runner =
new Thread() {
public void run() {
PrintWriter fw = null;
StringWriter sw = null;
PrintWriter pw = null;
BufferedReader in = null;
try {
if ( connection instanceof HttpURLConnection ) {
// read and store cookies
Map map = ( ( HttpURLConnection ) connection ).getHeaderFields();
for ( Iterator it = map.keySet().iterator(); it.hasNext(); ) {
String name = ( String ) it.next();
if ( name != null && name.equals( "Set-Cookie" ) ) {
List cookies = ( List ) map.get( name );
for ( Iterator c = cookies.iterator(); c.hasNext(); ) {
String raw = ( String ) c.next();
Cookie cookie = new Cookie( raw );
cookieStorage.put( cookie.getId(), cookie );
}
}
}
// maybe log response headers
if ( verbose ) {
log( String.valueOf( ( ( HttpURLConnection ) connection ).getResponseCode() ) );
log( ( ( HttpURLConnection ) connection ).getResponseMessage() );
StringBuffer sb = new StringBuffer();
map = ( ( HttpURLConnection ) connection ).getHeaderFields();
for ( Iterator it = map.keySet().iterator(); it.hasNext(); ) {
String name = ( String ) it.next();
sb.append( name ).append( "=" );
List values = ( List ) map.get( name );
if ( values != null ) {
if ( values.size() == 1 )
sb.append( values.get( 0 ) );
else if ( values.size() > 1 ) {
sb.append( "[" );
for ( Iterator v = values.iterator(); v.hasNext(); ) {
sb.append( v.next() ).append( "," );
}
sb.append( "]" );
}
}
sb.append( "\n" );
log( sb.toString() );
}
}
}
in = new BufferedReader(
new InputStreamReader( connection.getInputStream() ) );
if ( log != null ) {
// user wants output stored to a file
fw = new PrintWriter( new FileWriter( log, append ) );
}
if ( property != null ) {
// user wants output stored in a property
sw = new StringWriter();
pw = new PrintWriter( sw );
}
String line;
while ( null != ( ( line = in.readLine() ) ) ) {
if ( currentRunner != this ) {
break;
}
if ( verbose ) {
log( line );
}
if ( fw != null ) {
// write response to a file
fw.println( line );
}
if ( pw != null ) {
// write response to a property
pw.println( line );
}
}
}
catch ( Exception e ) {
//e.printStackTrace();
if ( failOnError ) {
throw new BuildException( e, getLocation() );
}
}
finally {
try {
in.close();
}
catch ( Exception e ) {
// ignored
}
try {
if ( fw != null ) {
fw.flush();
fw.close();
}
}
catch ( Exception e ) {
// ignored
}
}
if ( property != null && sw != null ) {
// save property
getProject().setProperty( property, sw.toString() );
}
}
};
currentRunner = runner;
runner.start();
runner.join( maxwait );
if ( runner.isAlive() ) {
currentRunner = null;
runner.interrupt();
if ( failOnError ) {
throw new BuildException( "maxwait exceeded, unable to receive data", getLocation() );
}
}
}
if ( verbose )
log( "Post complete." );
}
catch ( Exception e ) {
if ( failOnError ) {
throw new BuildException( e );
}
}
}
/**
* Borrowed from Property -- load variables from a file
*
* @param file file to load
* @exception BuildException Description of the Exception
*/
private void loadFile( File file ) throws BuildException {
Properties fileprops = new Properties();
try {
if ( file.exists() ) {
FileInputStream fis = new FileInputStream( file );
try {
fileprops.load( fis );
}
finally {
if ( fis != null ) {
fis.close();
}
}
addProperties( fileprops );
}
else {
log( "Unable to find property file: " + file.getAbsolutePath(),
Project.MSG_VERBOSE );
}
log( "Post complete." );
}
catch ( Exception e ) {
if ( failOnError ) {
throw new BuildException( e );
}
}
}
/**
* Builds and formats the message to send to the server. Message is UTF-8
* encoded unless encoding has been explicitly set.
*
* @return the message to send to the server.
*/
private String getContent() {
if ( propsFile != null ) {
loadFile( propsFile );
}
if ( textProps != null ) {
loadTextProps( textProps );
}
StringBuffer content = new StringBuffer();
try {
Enumeration en = props.keys();
while ( en.hasMoreElements() ) {
String name = ( String ) en.nextElement();
String value = ( String ) props.get( name );
content.append( URLEncoder.encode( name, encoding ) );
content.append( "=" );
content.append( URLEncoder.encode( value, encoding ) );
if ( en.hasMoreElements() ) {
content.append( "&" );
}
}
}
catch ( IOException ex ) {
if ( failOnError ) {
throw new BuildException( ex, getLocation() );
}
}
return content.toString();
}
/**
* Description of the Method
*
* @param tp
*/
private void loadTextProps( String tp ) {
Properties p = new Properties();
Project project = getProject();
StringTokenizer st = new StringTokenizer( tp, "$" );
while ( st.hasMoreTokens() ) {
String token = st.nextToken();
int start = token.indexOf( "{" );
int end = token.indexOf( "}" );
if ( start > -1 && end > -1 && end > start ) {
String name = token.substring( start + 1, end - start );
String value = project.getProperty( name );
if ( value != null )
p.setProperty( name, value );
}
}
addProperties( p );
}
/**
* Borrowed from Property -- iterate through a set of properties, resolve
* them, then assign them
*
* @param fileprops The feature to be added to the Properties attribute
*/
private void addProperties( Properties fileprops ) {
resolveAllProperties( fileprops );
Enumeration e = fileprops.keys();
while ( e.hasMoreElements() ) {
String name = ( String ) e.nextElement();
String value = fileprops.getProperty( name );
props.put( name, value );
}
}
/**
* Borrowed from Property -- resolve properties inside a properties
* hashtable
*
* @param fileprops Description of the Parameter
* @exception BuildException Description of the Exception
*/
private void resolveAllProperties( Properties fileprops ) throws BuildException {
for ( Enumeration e = fileprops.keys(); e.hasMoreElements(); ) {
String name = ( String ) e.nextElement();
String value = fileprops.getProperty( name );
boolean resolved = false;
while ( !resolved ) {
Vector fragments = new Vector();
Vector propertyRefs = new Vector();
/// this is the Ant 1.5 way of doing it. The Ant 1.6 PropertyHelper
/// should be used -- eventually.
ProjectHelper.parsePropertyString( value, fragments,
propertyRefs );
resolved = true;
if ( propertyRefs.size() != 0 ) {
StringBuffer sb = new StringBuffer();
Enumeration i = fragments.elements();
Enumeration j = propertyRefs.elements();
while ( i.hasMoreElements() ) {
String fragment = ( String ) i.nextElement();
if ( fragment == null ) {
String propertyName = ( String ) j.nextElement();
if ( propertyName.equals( name ) ) {
throw new BuildException( "Property " + name
+ " was circularly "
+ "defined." );
}
fragment = getProject().getProperty( propertyName );
if ( fragment == null ) {
if ( fileprops.containsKey( propertyName ) ) {
fragment = fileprops.getProperty( propertyName );
resolved = false;
}
else {
fragment = "${" + propertyName + "}";
}
}
}
sb.append( fragment );
}
value = sb.toString();
fileprops.put( name, value );
}
}
}
}
/**
* Represents a cookie. See RFC 2109 and 2965.
*/
public class Cookie {
private String name;
private String value;
private String domain;
private String path = "/";
private String id;
/**
* @param raw the raw string abstracted from the header of an http response
* for a single cookie.
*/
public Cookie( String raw ) {
String[] args = raw.split( "[;]" );
for ( int i = 0; i < args.length; i++ ) {
String part = args[ i ];
int eq_index = part.indexOf("=");
if (eq_index == -1)
continue;
String first_part = part.substring(0, eq_index).trim();
String second_part = part.substring(eq_index + 1);
if ( i == 0 ) {
name = first_part;
value = second_part;
}
else if ( first_part.equalsIgnoreCase( "Path" ) )
path = second_part;
else if ( first_part.equalsIgnoreCase( "Domain" ) )
domain = second_part;
}
if (name == null)
throw new IllegalArgumentException("Raw cookie does not contain a cookie name.");
if (path == null)
path = "/";
setId(path, name);
}
/**
* @param name name of the cookie
* @param value the value of the cookie
*/
public Cookie( String name, String value ) {
if (name == null)
throw new IllegalArgumentException("Cookie name may not be null.");
this.name = name;
this.value = value;
setId(name);
}
/**
* @return the id of the cookie, used internally by Post to store the cookie
* in a hashtable.
*/
public String getId() {
if (id == null)
setId(path, name);
return id.toString();
}
private void setId(String name) {
setId(path, name);
}
private void setId(String path, String name) {
if (name == null)
name = "";
id = path + name;
}
/**
* @return the name of the cookie
*/
public String getName() {
return name;
}
/**
* @return the value of the cookie
*/
public String getValue() {
return value;
}
/**
* @param domain the domain of the cookie
*/
public void setDomain( String domain ) {
this.domain = domain;
}
/**
* @return the domain of the cookie
*/
public String getDomain() {
return domain;
}
/**
* @param path the path of the cookie
*/
public void setPath( String path ) {
this.path = path;
}
/**
* @return the path of the cookie
*/
public String getPath() {
return path;
}
/**
* @return a Cookie formatted as a Cookie Version 1 string. The returned
* string is suitable for including with an http request.
*/
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append( name ).append( "=" ).append( value ).append( ";" );
if ( domain != null )
sb.append( "Domain=" ).append( domain ).append( ";" );
if ( path != null )
sb.append( "Path=" ).append( path ).append( ";" );
sb.append( "Version=\"1\";" );
return sb.toString();
}
}
}