/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2004 Danet GmbH (www.danet.de), GS-AN.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Id: BrowserTool.java 1607 2006-09-29 12:32:13Z drmlipp $
*
* $Log$
* Revision 1.1.1.1 2004/08/18 15:17:39 drmlipp
* Update to 1.2
*
* Revision 1.5 2004/04/07 20:42:24 lipp
* Added authentication.
*
* Revision 1.4 2004/04/07 19:47:35 lipp
* Added proxy support.
*
* Revision 1.3 2004/04/07 15:46:52 lipp
* Using tagsoup now instead of jtidy.
*
* Revision 1.2 2004/04/06 21:19:07 lipp
* Several fixes.
*
* Revision 1.1 2004/04/06 15:34:57 lipp
* Initial version.
*
*/
package de.danet.an.workflow.tools.webclient;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import java.rmi.RemoteException;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpRecoverableException;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.ccil.cowan.tagsoup.Parser;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import de.danet.an.workflow.util.SAXEventBufferImpl;
import de.danet.an.workflow.api.Activity;
import de.danet.an.workflow.api.FormalParameter;
import de.danet.an.workflow.spis.aii.ApplicationNotStoppedException;
import de.danet.an.workflow.spis.aii.CannotExecuteException;
import de.danet.an.workflow.spis.aii.ContextRequester;
import de.danet.an.workflow.spis.aii.ResultProvider;
import de.danet.an.workflow.spis.aii.ResultProvider.ExceptionResult;
import de.danet.an.workflow.spis.aii.ToolAgent;
import de.danet.an.workflow.spis.aii.ToolAgentContext;
/**
* This class provides a tool for accessing HTML pages in the internet.
*
* @author <a href="mailto:lipp@danet.de">Michael Lipp</a>
* @version $Revision: 1607 $
*/
public class BrowserTool
implements ToolAgent, ResultProvider, ContextRequester {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog(BrowserTool.class);
private static String proxyHost = System.getProperty ("http.proxyHost");
private static int proxyPort = 80;
private static String nonProxyHosts
= System.getProperty ("http.nonProxyHosts");
private static Pattern nonProxyHostsPattern = null;
static {
String port = System.getProperty ("http.proxyPort");
if (proxyHost != null && port != null) {
proxyPort = Integer.parseInt(port);
}
if (nonProxyHosts != null) {
String s = Pattern.compile("\\.")
.matcher(nonProxyHosts).replaceAll("\\\\.");
s = Pattern.compile("\\*").matcher(s).replaceAll(".*?");
s = Pattern.compile("\\|").matcher("(" + s + ")").replaceAll(")|(");
logger.debug ("http.nonProxyHosts pattern is: " + s);
nonProxyHostsPattern = Pattern.compile (s);
}
}
/** Authenticate? */
private boolean authenticate = false;
private String authRealm = null;
private String authUsername = null;
private String authPassword = null;
/** The result container. */
private ThreadLocal result = new ThreadLocal ();
/** The cached workflow engine context. */
private ThreadLocal ctx = new ThreadLocal ();
/**
* Creates an instance of <code>BrowserTool</code>
* with all attributes initialized to default values.
*/
public BrowserTool () {
}
/**
* Determine if request is to be authenticated.
* @param value "<code>true</code>" or "<code>false</code>"
*/
public void setAuthenticate (String value) {
authenticate = Boolean.valueOf(value).booleanValue ();
}
/**
* Set the realm used for authentication. If set, implies that the
* request is authticated, and no realm parameter is expected as
* argument to invoke.
* @param value the value to be used
*/
public void setRealm (String value) {
authRealm = value;
authenticate = true;
}
/**
* Set the username used for authentication. If set, implies that
* the request is authticated, and no username parameter is
* expected as argument to invoke.
* @param value the value to be used
*/
public void setUsername (String value) {
authUsername = value;
authenticate = true;
}
/**
* Set the password used for authentication. If set, implies that
* the request is authticated, and no password parameter is
* expected as argument to invoke.
* @param value the value to be used
*/
public void setPassword (String value) {
authPassword = value;
authenticate = true;
}
/**
* Makes an engine context available to the tool agent.
* @param context the engine context
*/
public void setToolAgentContext (ToolAgentContext context) {
ctx.set(context);
}
// Implementation of de.danet.an.workflow.spis.aii.ToolAgent
/* Comment copied from interface. */
public void invoke(Activity act, FormalParameter[] formPars, Map actPars)
throws CannotExecuteException, RemoteException {
int paramIdx = 0;
String statusPN = formPars[paramIdx++].id();
String resultPN = formPars[paramIdx++].id();
String realm = authRealm;
String username = authUsername;
String password = authPassword;
if (authenticate) {
if (realm == null) {
realm = (String)actPars.get(formPars[paramIdx++].id());
}
if (username == null) {
username = (String)actPars.get(formPars[paramIdx++].id());
}
if (password == null) {
password = (String)actPars.get(formPars[paramIdx++].id());
}
}
String methodPN = formPars[paramIdx++].id();
String urlPN = formPars[paramIdx++].id();
HttpClient client = new HttpClient ();
String url = (String)actPars.get (urlPN);
if (url == null || url.length() == 0) {
throw new CannotExecuteException
("Second parameter must be valid url.");
}
String methodParm = (String)actPars.get (methodPN);
HttpMethod method = null;
if (methodParm.equals ("GET")) {
method = new GetMethod (url);
} else if (methodParm.equals ("POST")) {
method = new PostMethod (url);
} else {
throw new CannotExecuteException
("First parameter must be \"GET\" or \"POST\", is \""
+ methodParm + "\"");
}
HostConfiguration hc = method.getHostConfiguration();
if (hc != null && proxyHost != null) {
if (nonProxyHostsPattern == null
|| !nonProxyHostsPattern.matcher(hc.getHost()).matches ()) {
if (logger.isDebugEnabled()) {
logger.debug ("Using proxy to access " + url);
}
hc.setProxy(proxyHost, proxyPort);
} else {
if (logger.isDebugEnabled()) {
logger.debug ("Accessing " + url + " directly");
}
}
}
if (authenticate) {
client.getState().setCredentials
(realm, hc.getHost (),
new UsernamePasswordCredentials (username, password));
}
int statusCode = -1;
try {
for (int attempts = 0; attempts < 3; attempts += 1) {
try {
statusCode = client.executeMethod (method);
break;
} catch (HttpRecoverableException e) {
logger.info ("Recoverable exception accessing \""
+ url + "\": " + e.getMessage ());
}
}
} catch (IOException e) {
logger.warn ("Problem accessing \"" + url
+ "\" (abandoning): " + e.getMessage ());
result.set (new ExceptionResult ("CannotAccessException"));
return;
}
if (statusCode == -1) {
result.set (new ExceptionResult
("RetryCountExceededException"));
return;
}
Map res = new HashMap ();
result.set(res);
res.put (statusPN, new Integer (statusCode));
InputStream in = null;
try {
in = method.getResponseBodyAsStream();
} catch (IOException e) {
String msg = "Cannot get response body as stream: "
+ e.getMessage ();
logger.error (msg, e);
throw new CannotExecuteException (msg);
}
if (in == null) {
res.put (resultPN, null);
return;
}
Parser parser = new Parser ();
SAXEventBufferImpl sb = new SAXEventBufferImpl ();
parser.setContentHandler(sb);
try {
parser.parse(new InputSource (in));
} catch (IOException e) {
throw new CannotExecuteException
("Cannot parse response body: " + e.getMessage ());
} catch (SAXException e) {
throw new CannotExecuteException
("Cannot parse response body: " + e.getMessage ());
}
sb.pack ();
res.put (resultPN, sb);
}
/**
* Return the result evaluated during {@link ToolAgent#invoke
* <code>invoke</code>}. The method will only be called once after
* each invoke, i.e. the attribute holding the result be be
* cleared in this method.
*
* @return the result data or <code>null</code> if the invocation
* does not return any data.
*/
public Object result () {
Object res = result.get();
result.set (null);
return res;
}
/* Comment copied from interface. */
public void terminate(Activity activity)
throws ApplicationNotStoppedException, RemoteException {
throw new ApplicationNotStoppedException
("Terminate not implemented for BrowserTool.");
}
}