package com.limegroup.gnutella.licenses;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.io.ObjectInputStream;
import com.limegroup.gnutella.http.HttpClientManager;
import com.limegroup.gnutella.util.ProcessingQueue;
import com.limegroup.gnutella.util.CommonUtils;
import org.apache.xerces.parsers.DOMParser;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.w3c.dom.Node;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.URI;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
/**
* A base license class, implementing common functionality.
*/
abstract class AbstractLicense implements NamedLicense, Serializable, Cloneable {
private static final Log LOG = LogFactory.getLog(AbstractLicense.class);
/**
* The queue that all license verification attempts are processed in.
*/
private static final ProcessingQueue VQUEUE = new ProcessingQueue("LicenseVerifier");
private static final long serialVersionUID = 6508972367931096578L;
/** Whether or not this license has been verified. */
protected transient int verified = UNVERIFIED;
/** The URI where verification will be performed. */
protected transient URI licenseLocation;
/** The license name. */
private transient String licenseName;
/** The last time this license was verified. */
private long lastVerifiedTime;
/** Constructs a new AbstractLicense. */
AbstractLicense(URI uri) {
this.licenseLocation = uri;
}
public void setLicenseName(String name) { this.licenseName = name; }
public boolean isVerifying() { return verified == VERIFYING; }
public boolean isVerified() { return verified == VERIFIED; }
public String getLicenseName() { return licenseName; }
public URI getLicenseURI() { return licenseLocation; }
public long getLastVerifiedTime() { return lastVerifiedTime; }
/**
* Assume that all serialized licenses were verified.
* (Otherwise they wouldn't have been serialized.
*/
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
verified = VERIFIED;
}
/**
* Clears all internal state that could be set while verifying.
*/
protected abstract void clear();
/**
* Starts verification of the license.
*
* The listener is notified when verification is finished.
*/
public void verify(VerificationListener listener) {
verified = VERIFYING;
clear();
VQUEUE.add(new Verifier(listener));
}
/**
* Retrieves the body of a URL from a webserver.
*
* Returns null if the page could not be found.
*/
protected String getBody(String url) {
return getBodyFromURL(url);
}
/**
* Contacts the given URL and downloads returns the body of the
* HTTP request.
*/
protected String getBodyFromURL(String url) {
if(LOG.isTraceEnabled())
LOG.trace("Contacting: " + url);
HttpClient client = HttpClientManager.getNewClient();
GetMethod get = new GetMethod(url);
get.addRequestHeader("User-Agent", CommonUtils.getHttpServer());
try {
HttpClientManager.executeMethodRedirecting(client, get);
return get.getResponseBodyAsString();
} catch(IOException ioe) {
LOG.warn("Can't contact license server: " + url, ioe);
return null;
} finally {
get.releaseConnection();
}
}
/** Parses the document node of the XML. */
protected abstract void parseDocumentNode(Node node, boolean liveData);
/**
* Attempts to parse the given XML.
* The actual handling of the XML is sent to parseDocumentNode,
* which subclasses can implement as they see fit.
*
* If this is a request directly from our Verifier, 'liveData' is true.
* Subclasses may use this to know where the XML data is coming from.
*/
protected void parseXML(String xml, boolean liveData) {
if(xml == null)
return;
if(LOG.isTraceEnabled())
LOG.trace("Attempting to parse: " + xml);
DOMParser parser = new DOMParser();
InputSource is = new InputSource(new StringReader(xml));
try {
parser.parse(is);
} catch (IOException ioe) {
LOG.debug("IOX parsing XML\n" + xml, ioe);
return;
} catch (SAXException saxe) {
LOG.debug("SAX parsing XML\n" + xml, saxe);
return;
}
parseDocumentNode(parser.getDocument().getDocumentElement(), liveData);
}
/**
* Runnable that actually does the verification.
* This will retrieve the body of a webpage from the licenseURI,
* parse it, set the last verified time, and cache it in the LicenseCache.
*/
private class Verifier implements Runnable {
private final VerificationListener vc;
Verifier(VerificationListener listener) {
vc = listener;
}
public void run() {
String body = getBody(getLicenseURI().toString());
parseXML(body, true);
lastVerifiedTime = System.currentTimeMillis();
verified = VERIFIED;
LicenseCache.instance().addVerifiedLicense(AbstractLicense.this);
if(vc != null)
vc.licenseVerified(AbstractLicense.this);
}
}
}