From 7e72ec21d0a4a66ae8a6febad1ebb7c240905793 Mon Sep 17 00:00:00 2001 From: "Eric J. Bowersox" Date: Thu, 30 Dec 2004 05:55:01 +0000 Subject: [PATCH] added initial trackback code - the TrackbackManager which manages scanning outside Web pages for trackback resources and sending trackback pings --- build.properties.sample | 5 + build.xml | 7 + .../silverwrist/util/DOMElementHelper.java | 111 +- .../venice/except/SupplyFaultCode.java | 15 +- .../venice/except/TrackbackException.java | 76 ++ .../venice/except/VeniceException.java | 58 +- .../silverwrist/venice/std/TrackbackItem.java | 179 +++ .../venice/std/TrackbackManager.java | 1084 +++++++++++++++++ 8 files changed, 1470 insertions(+), 65 deletions(-) create mode 100644 src/com/silverwrist/venice/except/TrackbackException.java create mode 100644 src/com/silverwrist/venice/std/TrackbackItem.java create mode 100644 src/com/silverwrist/venice/std/TrackbackManager.java diff --git a/build.properties.sample b/build.properties.sample index 6d9b567..d6710ee 100644 --- a/build.properties.sample +++ b/build.properties.sample @@ -40,6 +40,11 @@ codec.base=/usr/local/java/commons-codec-1.2 # codec.lib=${codec.base} # codec.jarfile=commons-codec-1.2.jar +# [Location of Commons HTTP Client Library 2.0.2] +httpclient.base=/usr/local/java/commons-httpclient-2.0.2 +# httpclient.lib=${httpclient.base} +# httpclient.jarfile=commons-httpclient-2.0.2.jar + # [Location of Jakarta Regexp Library 1.3] regexp.base=/usr/local/java/jakarta-regexp-1.3 # regexp.lib=${regexp.base} diff --git a/build.xml b/build.xml index e8f2228..27e8353 100644 --- a/build.xml +++ b/build.xml @@ -53,6 +53,11 @@ + + + + + @@ -92,6 +97,7 @@ + @@ -183,6 +189,7 @@ + diff --git a/src/com/silverwrist/util/DOMElementHelper.java b/src/com/silverwrist/util/DOMElementHelper.java index fce632b..14aece5 100644 --- a/src/com/silverwrist/util/DOMElementHelper.java +++ b/src/com/silverwrist/util/DOMElementHelper.java @@ -20,11 +20,11 @@ package com.silverwrist.util; import org.w3c.dom.*; /** - * A class which wraps around the DOM Element class, providing some + * A class which wraps around the DOM {@link org.w3c.dom.Element Element} class, providing some * additional functionality. * - * @author Eric J. Bowersox <erbo@silcom.com> - * @version X + * @author Eric J. Bowersox <erbo@ricochet.com> + * @version $Id$ * @see org.w3c.dom.Element */ public final class DOMElementHelper @@ -34,7 +34,8 @@ public final class DOMElementHelper *-------------------------------------------------------------------------------- */ - private Element elt; // element housed by this helper class + /** The {@link org.w3c.dom.Element Element} housed by this helper class. */ + private final Element elt; /*-------------------------------------------------------------------------------- * Constructor @@ -42,9 +43,9 @@ public final class DOMElementHelper */ /** - * Constructs a new DOMElementHelper to wrap a specific Element. + * Constructs a new DOMElementHelper to wrap a specific {@link org.w3c.dom.Element Element}. * - * @param elt The Element to be wrapped. + * @param elt The Element to be wrapped. */ public DOMElementHelper(Element elt) { @@ -58,12 +59,12 @@ public final class DOMElementHelper */ /** - * Returns the content of all text nodes underneath a specified Element, concatenated + * Returns the content of all text nodes underneath a specified {@link org.w3c.dom.Element Element}, concatenated * together into a single string. * - * @param e The Element to extract text from. - * @return The text content under this Element node. If the specified Element - * has no text nodes underneath it, returns null. + * @param e The Element to extract text from. + * @return The text content under this Element node. If the specified Element + * has no text nodes underneath it, returns null. */ private static final String getTextOfElement(Element e) { @@ -95,12 +96,12 @@ public final class DOMElementHelper } // end getTextOfElement /** - * Returns the value of the text of the specified Element, expressed as an integer. + * Returns the value of the text of the specified {@link org.w3c.dom.Element Element}, expressed as an integer. * - * @param e The Element to extract text from. - * @return An Integer object containing the value of the specified element. If - * the Element has no text, or if the text cannot be expressed as an integer, - * returns null. + * @param e The Element to extract text from. + * @return An {@link java.lang.Integer Integer} object containing the value of the specified element. If + * the Element has no text, or if the text cannot be expressed as an integer, + * returns null. */ private static final Integer getIntegerFromElement(Element e) { @@ -124,7 +125,7 @@ public final class DOMElementHelper */ /** - * Returns the Element wrapped by this object. + * Returns the {@link org.w3c.dom.Element Element} wrapped by this object. * * @return See above. */ @@ -135,12 +136,12 @@ public final class DOMElementHelper } // end getElement /** - * Searches for the first sub-element of the wrapped Element with the given name. + * Searches for the first sub-element of the wrapped {@link org.w3c.dom.Element Element} with the given name. * * @param name Name of the sub-element to search for. - * @return The first sub-element of the wrapped Element with the specified name. - * If the Element has no child Elements with the given name, - * the method returns null. + * @return The first sub-element of the wrapped Element with the specified name. + * If the Element has no child Elements with the given name, + * the method returns null. */ public final Element getSubElement(String name) { @@ -160,11 +161,11 @@ public final class DOMElementHelper } // end getSubElement /** - * Returns the content of all text nodes underneath the wrapped Element, concatenated + * Returns the content of all text nodes underneath the wrapped {@link org.w3c.dom.Element Element}, concatenated * together into a single string. * - * @return The text content under the wrapped Element node. If the wrapped Element - * has not text nodes underneath it, returns null. + * @return The text content under the wrapped Element node. If the wrapped Element + * has not text nodes underneath it, returns null. */ public final String getElementText() { @@ -173,11 +174,11 @@ public final class DOMElementHelper } // end getElementText /** - * Returns the value of the text of the wrapped Element, expressed as an integer. + * Returns the value of the text of the wrapped {@link org.w3c.dom.Element Element}, expressed as an integer. * - * @return An Integer object containing the value of the wrapped element. If - * the Element has no text, or if the text cannot be expressed as an integer, - * returns null. + * @return An {@link java.lang.Integer Integer} object containing the value of the wrapped element. If + * the Element has no text, or if the text cannot be expressed as an integer, + * returns null. */ public final Integer getElementInt() { @@ -187,12 +188,12 @@ public final class DOMElementHelper /** * Returns the content of all text nodes underneath the first sub-element of the wrapped - * Element, with the given name, concatenated together into a single string. + * {@link org.w3c.dom.Element Element}, with the given name, concatenated together into a single string. * * @param name The name of the sub-element to search for. - * @return The text content under the specified sub-element of the wrapped Element node. - * If the wrapped Element does not have a sub-element with the given name, or - * that sub-element has no text nodes underneath it, returns null. + * @return The text content under the specified sub-element of the wrapped Element node. + * If the wrapped Element does not have a sub-element with the given name, or + * that sub-element has no text nodes underneath it, returns null. */ public final String getSubElementText(String name) { @@ -203,13 +204,13 @@ public final class DOMElementHelper /** * Returns the value of the text underneath the first sub-element of the wrapped - * Element, with the given name, expressed as an integer. + * {@link org.w3c.dom.Element Element}, with the given name, expressed as an integer. * * @param name The name of the sub-element to search for. - * @return An Integer object containing the value of the specified element. If - * the wrapped Element does not have a sub-element with the given name, or that + * @return An {@link java.lang.Integer Integer} object containing the value of the specified element. If + * the wrapped Element does not have a sub-element with the given name, or that * sub-element has no text, or if the text cannot be expressed as an integer, returns - * null. + * null. */ public final Integer getSubElementInt(String name) { @@ -219,11 +220,11 @@ public final class DOMElementHelper } // end getSubElementInt /** - * Determines whether the wrapped Element has a sub-element with the given name. + * Determines whether the wrapped {@link org.w3c.dom.Element Element} has a sub-element with the given name. * * @param name Name of the sub-element to search for. - * @return true if the wrapped Element has a sub-element with the - * specified name, false if not. + * @return true if the wrapped Element has a sub-element with the + * specified name, false if not. */ public final boolean hasChildElement(String name) { @@ -232,11 +233,11 @@ public final class DOMElementHelper } // end hasChildElement /** - * Determines whether the wrapped Element has an attribute with the given name. + * Determines whether the wrapped {@link org.w3c.dom.Element Element} has an attribute with the given name. * * @param name Name of the attribute to search for. - * @return true if the wrapped Element has an attribute with the - * specified name, false if not. + * @return true if the wrapped Element has an attribute with the + * specified name, false if not. */ public final boolean hasAttribute(String name) { @@ -245,13 +246,13 @@ public final class DOMElementHelper } // end hasAttribute /** - * Returns the value of a specified attribute of the wrapped Element, expressed as + * Returns the value of a specified attribute of the wrapped {@link org.w3c.dom.Element Element}, expressed as * an integer. * * @param name Name of the attribute to search for. - * @return An Integer object containing the value of the specified attribute. If - * the wrapped Element has no such attribute, or if the attribute's value - * cannot be expressed as an integer, returns null. + * @return An {@link java.lang.Integer Integer} object containing the value of the specified attribute. If + * the wrapped Element has no such attribute, or if the attribute's value + * cannot be expressed as an integer, returns null. */ public final Integer getAttributeInt(String name) { @@ -271,6 +272,15 @@ public final class DOMElementHelper } // end getAttributeInt + /** + * Returns the value of a specified attribute of the wrapped {@link org.w3c.dom.Element Element}, expressed as + * a {@link java.lang.Boolean Boolean}. + * + * @param name Name of the attribute to search for. + * @return A Boolean object containing the value of the specified attribute. If + * the wrapped Element has no such attribute, or if the attribute's value + * cannot be expressed as a Boolean, returns null. + */ public final Boolean getAttributeBoolean(String name) { String tmp = elt.getAttribute(name); @@ -283,6 +293,17 @@ public final class DOMElementHelper } // end getAttributeBoolean + /** + * Returns the value of a specified attribute of the wrapped {@link org.w3c.dom.Element Element}, expressed as + * a {@link java.lang.Boolean Boolean}. + * + * @param name Name of the attribute to search for. + * @param default_val Default value to return if the attribute does not exist. + * @return A Boolean object containing the value of the specified attribute. If + * the wrapped Element has no such attribute, returns the Boolean equivalent of + * default_val. If the attribute's value cannot be expressed as a Boolean, + * returns null. + */ public final Boolean getAttributeBoolean(String name, boolean default_val) { if (this.hasAttribute(name)) diff --git a/src/com/silverwrist/venice/except/SupplyFaultCode.java b/src/com/silverwrist/venice/except/SupplyFaultCode.java index b287205..87f6084 100644 --- a/src/com/silverwrist/venice/except/SupplyFaultCode.java +++ b/src/com/silverwrist/venice/except/SupplyFaultCode.java @@ -9,16 +9,27 @@ * * The Original Code is the Venice Web Communities System. * - * The Initial Developer of the Original Code is Eric J. Bowersox , + * The Initial Developer of the Original Code is Eric J. Bowersox , * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are - * Copyright (C) 2002 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * Copyright (C) 2002-2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. * * Contributor(s): */ package com.silverwrist.venice.except; +/** + * An interface allowing exceptions to be turned into XML-RPC "fault codes." + * + * @author Eric J. Bowersox <erbo@ricochet.com> + * @version $Id$ + */ public interface SupplyFaultCode { + /** + * Returns the XML-RPC "fault code" associated with this exception. + * + * @return See above. + */ public abstract int getFaultCode(); } // end interface SupplyFaultCode diff --git a/src/com/silverwrist/venice/except/TrackbackException.java b/src/com/silverwrist/venice/except/TrackbackException.java new file mode 100644 index 0000000..426673e --- /dev/null +++ b/src/com/silverwrist/venice/except/TrackbackException.java @@ -0,0 +1,76 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2001-2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.except; + +/** + * An exception thrown by trackback operations. + * + * @author Eric J. Bowersox <erbo@ricochet.com> + * @version $Id$ + */ +public class TrackbackException extends VeniceException +{ + /*-------------------------------------------------------------------------------- + * Constructors + *-------------------------------------------------------------------------------- + */ + + /** + * Constructs a new TrackbackException. + */ + public TrackbackException() + { + super(); + + } // end constructor + + /** + * Constructs a new TrackbackException with a text message. + * + * @param msg The message to set in this exception. + */ + public TrackbackException(String msg) + { + super(msg); + + } // end constructor + + /** + * Constructs a new TrackbackException wrapping another exception. + * + * @param inner The exception wrapped by this one. + */ + public TrackbackException(Throwable inner) + { + super(inner); + + } // end constructor + + /** + * Constructs a new TrackbackException wrapping another exception. + * + * @param msg The message to set in this exception. + * @param inner The exception wrapped by this one. + */ + public TrackbackException(String msg, Throwable inner) + { + super(msg,inner); + + } // end constructor + +} // end class TrackbackException diff --git a/src/com/silverwrist/venice/except/VeniceException.java b/src/com/silverwrist/venice/except/VeniceException.java index e78071d..25a7878 100644 --- a/src/com/silverwrist/venice/except/VeniceException.java +++ b/src/com/silverwrist/venice/except/VeniceException.java @@ -9,9 +9,9 @@ * * The Original Code is the Venice Web Communities System. * - * The Initial Developer of the Original Code is Eric J. Bowersox , + * The Initial Developer of the Original Code is Eric J. Bowersox , * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are - * Copyright (C) 2001-02 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * Copyright (C) 2001-2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. * * Contributor(s): */ @@ -22,10 +22,10 @@ import java.io.PrintWriter; /** * The root exception of all exceptions thrown by the Venice core code. It is capable of "wrapping" - * another exception within it. + * another exception within it. (Predating the support for this in JDK 1.4, of course.) * - * @author Eric J. Bowersox <erbo@silcom.com> - * @version X + * @author Eric J. Bowersox <erbo@ricochet.com> + * @version $Id$ */ public class VeniceException extends Exception implements SupplyFaultCode { @@ -34,7 +34,8 @@ public class VeniceException extends Exception implements SupplyFaultCode *-------------------------------------------------------------------------------- */ - private Throwable inner = null; // internal "root cause" exception + /** Internal "root cause" exception for this exception. */ + private final Throwable inner; /*-------------------------------------------------------------------------------- * Constructors @@ -42,27 +43,29 @@ public class VeniceException extends Exception implements SupplyFaultCode */ /** - * Constructs a new VeniceException. + * Constructs a new VeniceException. */ public VeniceException() { super(); + this.inner = null; } // end constructor /** - * Constructs a new VeniceException with a text message. + * Constructs a new VeniceException with a text message. * * @param msg The message to set in this exception. */ public VeniceException(String msg) { super(msg); + this.inner = null; } // end constructor /** - * Constructs a new VeniceException wrapping another exception. + * Constructs a new VeniceException wrapping another exception. * * @param inner The exception wrapped by this one. */ @@ -74,7 +77,7 @@ public class VeniceException extends Exception implements SupplyFaultCode } // end constructor /** - * Constructs a new VeniceException wrapping another exception. + * Constructs a new VeniceException wrapping another exception. * * @param msg The message to set in this exception. * @param inner The exception wrapped by this one. @@ -104,10 +107,10 @@ public class VeniceException extends Exception implements SupplyFaultCode } // end printStackTrace /** - * Prints this exception and its backtrace to the specified PrintStream. Also prints the - * backtrace of any "wrapped" exception. + * Prints this exception and its backtrace to the specified {@link java.io.PrintStream PrintStream}. + * Also prints the backtrace of any "wrapped" exception. * - * @param s PrintStream to use for output. + * @param s PrintStream to use for output. */ public void printStackTrace(PrintStream s) { @@ -117,10 +120,10 @@ public class VeniceException extends Exception implements SupplyFaultCode } // end printStackTrace /** - * Prints this exception and its backtrace to the specified PrintWriter. Also prints the - * backtrace of any "wrapped" exception. + * Prints this exception and its backtrace to the specified {@link java.io.PrintWriter PrintWriter}. + * Also prints the backtrace of any "wrapped" exception. * - * @param s PrintWriter to use for output. + * @param s PrintWriter to use for output. */ public void printStackTrace(PrintWriter s) { @@ -134,9 +137,14 @@ public class VeniceException extends Exception implements SupplyFaultCode *-------------------------------------------------------------------------------- */ + /** + * Returns the XML-RPC "fault code" associated with this exception. + * + * @return See above. + */ public int getFaultCode() { - return 0; + return 0; // default return } // end getFaultCode @@ -145,6 +153,13 @@ public class VeniceException extends Exception implements SupplyFaultCode *-------------------------------------------------------------------------------- */ + /** + * Prints the supplied exception as a "root cause" to the given {@link java.io.PrintStream PrintStream}. + * + * @param s PrintStream to print the exception to. + * @param t The {@link java.lang.Throwable Throwable} to be printed. If this parameter is + * null, this function is a no-op. + */ public static void printRootCause(PrintStream s, Throwable t) { if (t!=null) @@ -156,6 +171,13 @@ public class VeniceException extends Exception implements SupplyFaultCode } // end printRootCause + /** + * Prints the supplied exception as a "root cause" to the given {@link java.io.PrintWriter PrintWriter}. + * + * @param s PrintWriter to print the exception to. + * @param t The {@link java.lang.Throwable Throwable} to be printed. If this parameter is + * null, this function is a no-op. + */ public static void printRootCause(PrintWriter s, Throwable t) { if (t!=null) @@ -173,7 +195,7 @@ public class VeniceException extends Exception implements SupplyFaultCode */ /** - * Returns the exception wrapped by this exception, or null if there is none. + * Returns the exception wrapped by this exception, or null if there is none. * * @return See above. */ diff --git a/src/com/silverwrist/venice/std/TrackbackItem.java b/src/com/silverwrist/venice/std/TrackbackItem.java new file mode 100644 index 0000000..ed0eb63 --- /dev/null +++ b/src/com/silverwrist/venice/std/TrackbackItem.java @@ -0,0 +1,179 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.std; + +import java.net.URL; +import java.util.*; +import org.apache.commons.httpclient.NameValuePair; +import com.silverwrist.venice.except.TrackbackException; + +/** + * A class representing a trackback item found on an external Web page via RDF auto-discovery. This + * conforms to the Moveable Type Trackback Protocol, Version 1.1. + * + * @author Eric J. Bowersox <erbo@ricochet.com> + * @version $Id$ + */ +public abstract class TrackbackItem +{ + /*-------------------------------------------------------------------------------- + * Attributes + *-------------------------------------------------------------------------------- + */ + + /** + * The URL of this particular trackback item. + */ + private final URL m_item; + + /** + * The trackback "ping URL" which is used to register trackbacks for this item. + */ + private final URL m_trackback; + + /** + * The title associated with this item. + */ + private final String m_title; + + /*-------------------------------------------------------------------------------- + * Abstract operations + *-------------------------------------------------------------------------------- + */ + + /** + * Send a trackback ping to this trackback URL. + * + * @param url The URL of the trackback item to send to. + * @param parms The parameters to pass to the trackback operation. + * @exception com.silverwrist.venice.except.TrackbackException If the trackback could not be sent. + */ + protected abstract void sendPing(String url, NameValuePair[] parms) throws TrackbackException; + + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + /** + * Creates a new TrackbackItem. + * + * @param item The URL of the trackback item. + * @param trackback The "trackback ping URL" for this particular trackback item. + * @param title The title associated with this trackback item. + */ + protected TrackbackItem(URL item, URL trackback, String title) + { + m_item = item; + m_trackback = trackback; + m_title = title; + + } // end constructor + + /*-------------------------------------------------------------------------------- + * Overrides from class Object + *-------------------------------------------------------------------------------- + */ + + /** + * Returns a string representation of the object. In general, the toString method returns a + * string that "textually represents" this object. + * + * @return A string representation of the object. + */ + public String toString() + { + StringBuffer buf = new StringBuffer("{TrackbackItem: item = "); + buf.append(m_item).append(", trackback ping = ").append(m_trackback).append(", title = \""); + buf.append(m_title).append("\"}"); + return buf.toString(); + + } // end toString + + /*-------------------------------------------------------------------------------- + * Public getters + *-------------------------------------------------------------------------------- + */ + + /** + * Returns the {@link java.net.URL URL} of this trackback item. + * + * @return The URL of this trackback item. + */ + public URL getItem() + { + return m_item; + + } // end getItem + + /** + * Returns the {@link java.net.URL URL} of the "trackback ping" URL for this trackback. + * + * @return The URL of the "trackback ping" URL for this trackback. + */ + public URL getTrackbackURL() + { + return m_trackback; + + } // end getTrackbackURL + + /** + * Returns the title of this trackback item. + * + * @return The title of this trackback item. + */ + public String getTitle() + { + return m_title; + + } // end getTitle + + /*-------------------------------------------------------------------------------- + * External operations + *-------------------------------------------------------------------------------- + */ + + /** + * Send a trackback ping to this trackback URL. + * + * @param permalink URL that is the "permalink" to the entity tracking back to this entry. + * @param blog_name Name of the blog that is linking to this entry. + * @param title Entry title of the entry that is linking to this entry. + * @param excerpt Excerpt of the entry that is linked to this entry. (Moveable Type limits this to 255 characters.) + * @exception com.silverwrist.venice.except.TrackbackException If the trackback could not be sent. + */ + public void sendPing(String permalink, String blog_name, String title, String excerpt) throws TrackbackException + { + // Construct the NameValuePairs that make up the fields. + ArrayList tmp = new ArrayList(4); + tmp.add(new NameValuePair("url",permalink)); + if (blog_name!=null) + tmp.add(new NameValuePair("blog_name",blog_name)); + if (title!=null) + tmp.add(new NameValuePair("title",title)); + if (excerpt!=null) + tmp.add(new NameValuePair("excerpt",excerpt)); + NameValuePair[] parms = new NameValuePair[tmp.size()]; + tmp.toArray(parms); + + // Pass to subfunction. + sendPing(m_trackback.toString(),parms); + + } // end sendPing + +} // end class TrackbackItem diff --git a/src/com/silverwrist/venice/std/TrackbackManager.java b/src/com/silverwrist/venice/std/TrackbackManager.java new file mode 100644 index 0000000..aa48b58 --- /dev/null +++ b/src/com/silverwrist/venice/std/TrackbackManager.java @@ -0,0 +1,1084 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.std; + +import java.io.*; +import java.net.*; +import java.text.*; +import java.util.*; +import javax.mail.internet.ContentType; +import javax.xml.parsers.*; +import org.apache.commons.httpclient.*; +import org.apache.commons.httpclient.methods.*; +import org.apache.commons.httpclient.util.*; +import org.apache.log4j.Logger; +import org.apache.regexp.*; +import org.w3c.dom.*; +import org.xml.sax.*; +import com.silverwrist.util.*; +import com.silverwrist.venice.except.TrackbackException; + +/** + * A Singleton class which implements the management of trackback items. The implementation conforms to + * the Moveable Type Trackback Protocol, Version 1.1. + * + * @author Eric J. Bowersox <erbo@ricochet.com> + * @version $Id$ + */ +public class TrackbackManager +{ + /*-------------------------------------------------------------------------------- + * Internal class which caches information about a particular page + *-------------------------------------------------------------------------------- + */ + + /** + * Internal class which caches information about a particular page read via HTTP. + * + * @author Eric J. Bowersox <erbo@ricochet.com> + * @version $Id$ + */ + private class PageAttributes + { + /*==================================================================== + * Attributes + *==================================================================== + */ + + /** Last modification date for this page. */ + private java.util.Date m_last_modified = null; + + /** Expiration time for the page. */ + private long m_expires = 0; + + /** Allow reset of expire time from page metadata if this flag is true. */ + private boolean m_expire_meta = true; + + /** List of all trackback items that were loaded on this page. */ + private final LinkedList m_tb_items; + + /*==================================================================== + * Constructor + *==================================================================== + */ + + /** + * Creates a new PageAttributes instance. + * + * @param response {@link org.apache.commons.httpclient.HttpMethod HttpMethod} containing response headers. + */ + PageAttributes(HttpMethod response) + { + Header hdr = null; + try + { // get the Last-Modified date from the response + hdr = response.getResponseHeader("Last-Modified"); + if (hdr!=null) + m_last_modified = DateParser.parseDate(hdr.getValue()); + else + m_last_modified = null; + + } // end try + catch (DateParseException e) + { // Last-Modified date cannot be parsed + logger.debug("failed to parse Last-Modified date",e); + m_last_modified = null; + + } // end catch + + boolean look_for_expire = true; + hdr = response.getResponseHeader("Cache-Control"); + if (hdr!=null) + { // first, try to match s-maxage + String s = null; + RE m = new RE(s_cache_smaxage,RE.MATCH_CASEINDEPENDENT); + if (m.match(hdr.getValue())) + s = m.getParen(1); + else + { // try to match max-age + m = new RE(s_cache_maxage,RE.MATCH_CASEINDEPENDENT); + if (m.match(hdr.getValue())) + s = m.getParen(1); + + } // end else + + if (s!=null) + { // parse the delta-seconds value and create an expiration time + m_expires = System.currentTimeMillis() + (Long.parseLong(s) * 1000L); + m_expire_meta = false; + look_for_expire = false; + + } // end if + + } // end if + + if (look_for_expire) + { // OK, didn't find it there, try here + java.util.Date d = null; + try + { // get the Expires date from the response + hdr = response.getResponseHeader("Expires"); + if (hdr!=null) + d = DateParser.parseDate(hdr.getValue()); + + } // end try + catch (DateParseException e) + { // Expires date cannot be parsed + logger.debug("failed to parse Expires date",e); + d = null; + + } // end catch + + if (d!=null) + m_expires = d.getTime(); // save time value + + } // end if + + m_tb_items = new LinkedList(); + + } // end constructor + + /*==================================================================== + * Public getters/setters + *==================================================================== + */ + + /** + * Returns the last modification date for the given page. + * + * @return See above. + */ + java.util.Date getLastModified() + { + return m_last_modified; + + } // end getLastModified + + /*==================================================================== + * External operations + *==================================================================== + */ + + /** + * Adds a new {@link com.silverwrist.venice.std.TrackbackItem TrackbackItem} to the list managed + * by this attributes object. + * + * @param tb The TrackbackItem to be added. + */ + void addItem(TrackbackItem tb) + { + m_tb_items.add(tb); + + } // end addItem + + /** + * Update the data in this PageAttributes object from the tags + * in the actual page. + * + * @param page_attrs The {@link java.util.Map Map} containing the data to use. + */ + void updateFromPage(Map page_attrs) + { + java.util.Date d = null; + String s = (String)(page_attrs.get("LAST-MODIFIED")); + try + { // attempt to parse this date + if (s!=null) + d = DateParser.parseDate(s); + + } // end try + catch (DateParseException e) + { // parse failed + logger.debug("failed to parse Last-Modified date",e); + d = null; + + } // end catch + + if (d!=null) // update last modified date + m_last_modified = d; + + if (m_expire_meta) + { // want to look for an expiration date as well + d = null; + s = (String)(page_attrs.get("EXPIRES")); + try + { // attempt to parse this date + if (s!=null) + d = DateParser.parseDate(s); + + } // end try + catch (DateParseException e) + { // parse failed + logger.debug("failed to parse Expires date",e); + d = null; + + } // end catch + + if (d!=null) // update expiration date + m_expires = d.getTime(); + + } // end if + + } // end updateFromPage + + /** + * Check to see if this entry has expired, and, if so, remove all mention of it from the item and + * page caches. + * + * @return true if the item has expired, false if not. + */ + boolean expire() + { + if (m_expires==0) + return false; + if (m_expires>=System.currentTimeMillis()) + return false; + synchronized (TrackbackManager.this) + { // need to frob m_item_cache and m_page_cache here... + for (Iterator it=m_tb_items.iterator(); it.hasNext(); ) + { // remove all items depending on this PageAttributes + TrackbackItem foo = (TrackbackItem)(it.next()); + m_item_cache.remove(foo.getItem()); + + } // end for + + ArrayList baleeted = new ArrayList(); + for (Iterator it=m_page_cache.entrySet().iterator(); it.hasNext(); ) + { // find the keys that point to this entry + Map.Entry ntry = (Map.Entry)(it.next()); + if (ntry.getValue()==this) + baleeted.add(ntry.getKey()); + + } // end for + + for (Iterator it=baleeted.iterator(); it.hasNext(); ) + m_page_cache.remove(it.next()); // purge out cache entries + + } // end synchronized block + + return true; + + } // end expire + + } // end class PageAttributes + + /*-------------------------------------------------------------------------------- + * Internal implementation of TrackbackItem + *-------------------------------------------------------------------------------- + */ + + /** + * Internal implementation of {@link com.silverwrist.venice.std.TrackbackItem TrackbackItem}. + * + * @author Eric J. Bowersox <erbo@ricochet.com> + * @version $Id$ + */ + private class MyTrackbackItem extends TrackbackItem + { + /*==================================================================== + * Attributes + *==================================================================== + */ + + /** + * The attributes for the page we found this trackback item on. + */ + private final PageAttributes m_attrs; + + /*==================================================================== + * Constructor + *==================================================================== + */ + + /** + * Constructs a new instance of MyTrackbackItem. + * + * @param item The URL of the trackback item. + * @param trackback The "trackback ping URL" for this particular trackback item. + * @param title The title associated with this trackback item. + * @param attrs The page attributes for the page we found the item on. + */ + MyTrackbackItem(URL item, URL trackback, String title, PageAttributes attrs) + { + super(item,trackback,title); + m_attrs = attrs; + m_attrs.addItem(this); + + } // end constructor + + /*==================================================================== + * Abstract implementations from class TrackbackItem + *==================================================================== + */ + + /** + * Send a trackback ping to this trackback URL. + * + * @param url The URL of the trackback item to send to. + * @param parms The parameters to pass to the trackback operation. + * @exception com.silverwrist.venice.except.TrackbackException If the trackback could not be sent. + */ + protected void sendPing(String url, NameValuePair[] parms) throws TrackbackException + { + // Construct the PostMethod and fill in the parameters. + PostMethod meth = new PostMethod(url); + meth.setDoAuthentication(false); + meth.setFollowRedirects(true); + meth.setRequestHeader("User-Agent",USER_AGENT); + meth.setRequestBody(parms); + + synchronized (TrackbackManager.this) + { // since we use the HTTP client and the parser... + boolean get_resp = false; + try + { // execute the method! + int rc = m_http_client.executeMethod(meth); + get_resp = true; + if (rc!=HttpStatus.SC_OK) // this is farked! + throw new TrackbackException("POST to " + url + " returned " + rc); + + // The response from the method will be XML. Parse it. + InputStream istm = meth.getResponseBodyAsStream(); + get_resp = false; + Document doc = m_tbresp_parser.parse(istm); + + // probe the returned XML tree + Element root = doc.getDocumentElement(); + if (!(root.getTagName().equals("response"))) + throw new TrackbackException("trackback response malformed - expected element"); + DOMElementHelper h = new DOMElementHelper(root); + String s = h.getSubElementText("error"); + if (s==null) + throw new TrackbackException("trackback response malformed - expected subelement of "); + if (s.equals("1")) + { // error - get message and throw it + s = h.getSubElementText("message"); + if (s!=null) + throw new TrackbackException("Trackback ping failed: " + s); + else + throw new TrackbackException("Trackback ping failed with no response given"); + + } // end if + else if (!(s.equals("0"))) + throw new TrackbackException("trackback response malformed - expected value of either 0 or 1"); + // else we were successful + + } // end try + catch (IOException e) + { // catch any I/O errors from executing the method + throw new TrackbackException("I/O error sending trackback ping: " + e.getMessage(),e); + + } // end catch + catch (SAXException e) + { // XML parser screwed up + throw new TrackbackException("trackback response malformed - not valid XML",e); + + } // end catch + finally + { // release the connection if possible + if (get_resp) + meth.getResponseBody(); + meth.releaseConnection(); + + } // end finally + + } // end synchronized block + + } // end sendPing + + /*==================================================================== + * Public getters/setters + *==================================================================== + */ + + /** + * Return the attributes from the page from which this trackback item was loaded. + * + * @return See above. + */ + PageAttributes getAttributes() + { + return m_attrs; + + } // end getAttributes + + /*==================================================================== + * External operations + *==================================================================== + */ + + /** + * Check to see if the page from which this trackback item was loaded has expired, and, if so, remove + * all mention of it from the item and page caches. + * + * @return true if the page from which this trackback item was loaded has expired, + * false if not. + */ + boolean expire() + { + return m_attrs.expire(); + + } // end expire + + } // end class MyTrackbackItem + + /*-------------------------------------------------------------------------------- + * Static data members + *-------------------------------------------------------------------------------- + */ + + /** The instance of {@link org.apache.log4j.Logger Logger} for this class's use. */ + private static Logger logger = Logger.getLogger(TrackbackManager.class); + + /** The Singleton instance of this class. */ + private static TrackbackManager s_self = null; + + /** The User-Agent string to use. */ + private static final String USER_AGENT = "Venice Web Communities System/0.01 (TrackbackManager)"; + + /** The XML namespace for RDF elements. */ + private static final String NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; + + /** The XML namespace for Dublin Core elements. */ + private static final String NS_DC = "http://purl.org/dc/elements/1.1/"; + + /* The XML namespace for trackback elements. */ + private static final String NS_TRACKBACK = "http://madskills.com/public/xml/rss/module/trackback/"; + + /** The date format to format output HTTP dates. */ + private static final DateFormat s_httpdate_format; + + /** The regular expression compiler we use. */ + private static final RECompiler COMPILER = new RECompiler(); + + /** Regular expression program to recognize the s-maxage directive in a Cache-Control header. */ + private static REProgram s_cache_smaxage = null; + + /** Regular expression program to recognize the max-age directive in a Cache-Control header. */ + private static REProgram s_cache_maxage = null; + + /** Regular expression program for recognizing META tags. */ + private static REProgram s_meta_tag = null; + + /** Regular expression program for recognizing HTTP-EQUIV attributes. */ + private static REProgram s_http_equiv_attr = null; + + /** Regular expression program for recognizing CONTENT attributes. */ + private static REProgram s_content_attr = null; + + /** Regular expression program for recognizing the start of an RDF block. */ + private static REProgram s_rdf_start = null; + + /*-------------------------------------------------------------------------------- + * Attributes + *-------------------------------------------------------------------------------- + */ + + /** Map from page {@link java.net.URL URL}s to PageAttributes objects. */ + private final Map m_page_cache; + + /** Map from trackback item {@link java.net.URL URL}s to MyTrackbackItem objects. */ + private final Map m_item_cache; + + /** Map from namespace names to end recognizer {@link org.apache.regexp.REProgram REProgram}s. */ + private final Map m_end_recognizers; + + /** + * The {@link org.apache.commons.httpclient.HttpClient HttpClient} to use to retrieve pages + * and send pings. + */ + private final HttpClient m_http_client; + + /** The instance of the XML parser we use to parse RDF blocks.*/ + private DocumentBuilder m_rdf_parser = null; + + /** The instance of the XML parser we use to parse trackback responses. */ + private DocumentBuilder m_tbresp_parser = null; + + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + /** + * Only one instance of this class can/should exist. + */ + private TrackbackManager() + { + m_page_cache = new HashMap(); + m_item_cache = new HashMap(); + m_end_recognizers = new HashMap(); + m_http_client = new HttpClient(); + + try + { // create the XML parsers we use + DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance(); + fact.setCoalescing(true); + fact.setExpandEntityReferences(true); + fact.setIgnoringComments(true); + fact.setNamespaceAware(true); + fact.setValidating(false); + m_rdf_parser = fact.newDocumentBuilder(); + fact.setCoalescing(true); + fact.setExpandEntityReferences(true); + fact.setIgnoringComments(true); + fact.setNamespaceAware(false); + fact.setValidating(false); + m_tbresp_parser = fact.newDocumentBuilder(); + + } // end try + catch (ParserConfigurationException e) + { // this is bad! + logger.fatal("XML parser creation failed",e); + + } // end catch + + } // end constructor + + /*-------------------------------------------------------------------------------- + * Internal operations + *-------------------------------------------------------------------------------- + */ + + /** + * Extracts an attribute value from the start of the string. The attribute value may be enclosed + * in quotes, or may simply be a series of nonblank characters delimited by blanks. + * + * @param s The string to extract the attribute value from. + * @return The attribute value extracted. + */ + private static final String extractAttribute(String s) + { + char[] a = s.toCharArray(); + int i = 0; + while ((i tags from the given page content and returns them + * as a {@link java.util.Map Map}. + * + * @param content The content of the page to scan. + * @return A non-modifiable Map containing all the tags from the page. + * The keys are the HTTP-EQUIV names, converted to upper-case. The values are the CONTENT values. + */ + private static final Map extractHttpEquivTags(String content) + { + Map rc = new HashMap(); + RE m = new RE(s_meta_tag,RE.MATCH_CASEINDEPENDENT); + int pos = 0; + while (m.match(content,pos)) + { // get the meta tag itself + String s = m.getParen(0); + RE m2 = new RE(s_http_equiv_attr,RE.MATCH_CASEINDEPENDENT); + if (m2.match(s)) + { // extract the HTTP-EQUIV name + String name = extractAttribute(s.substring(m2.getParenEnd(0))).toUpperCase(); + m2 = new RE(s_content_attr,RE.MATCH_CASEINDEPENDENT); + if (m2.match(s)) + { // extract the CONTENT value + String val = extractAttribute(s.substring(m2.getParenEnd(0))); + + // put the value into the return Map + String exist = (String)(rc.get(name)); + if (exist==null) + rc.put(name,val); + else + rc.put(name,exist + ", " + val); + + } // end if + // else ignore this one + + } // end if + // else ignore this one + + // reset position for next time + pos = m.getParenEnd(0); + + } // end while + + if (rc.isEmpty()) + return Collections.EMPTY_MAP; + else + return Collections.unmodifiableMap(rc); + + } // end extractHttpEquivTags + + /** + * Given a specific namespace tag, returns a {@link org.apache.regexp.REProgram REProgram} which recognizes + * the end-RDF tag. + * + * @param rdfns The namespace tag to use. + * @return The correct REProgram. + */ + private synchronized REProgram getEndRecognizer(String rdfns) + { + REProgram pgm = (REProgram)(m_end_recognizers.get(rdfns)); + if (pgm==null) + { // create the end recognizer expression and add it + StringBuffer buf = new StringBuffer(""); + try + { // compile the program and save it + pgm = COMPILER.compile(buf.toString()); + m_end_recognizers.put(rdfns,pgm); + + } // end try + catch (RESyntaxException e) + { // shouldn't happen + if (logger.isDebugEnabled()) + logger.debug("End pattern compilation of \"" + buf.toString() + "\" failed",e); + + } // end catch + + } // end if + + return pgm; + + } // end getEndRecognizer + + /** + * Loads the HTTP content at the specified URL, scans it for RDF description blocks, and adds those blocks + * as {@link com.silverwrist.venice.std.TrackbackItem TrackbackItem}s to our internal cache. Uses modification + * detection to keep from reloading a page unless necessary. + * + * @param url The URL of the resource to be loaded. + * @param attrs The attributes of the specified page; if this is null, we'll check the page + * cache for the right attributes. + * @return true if the page data was loaded and scanned for trackback items; false + * if no data was loaded (because it was not modified since the last time we loaded it, for instance). + * @exception com.silverwrist.venice.except.TrackbackException If there was an error loading or interpreting + * the page data. + */ + private synchronized boolean load(URL url, PageAttributes attrs) throws TrackbackException + { + if (attrs==null) + attrs = (PageAttributes)(m_page_cache.get(url)); + + // Create the GET method and set its headers. + String s = url.toString(); + int x = s.lastIndexOf('#'); + if (x>=0) + s = s.substring(0,x); + GetMethod getter = new GetMethod(s); + getter.setDoAuthentication(false); + getter.setFollowRedirects(true); + getter.setMethodRetryHandler(new DefaultMethodRetryHandler()); + getter.setRequestHeader("User-Agent",USER_AGENT); + getter.setRequestHeader("Accept","text/*"); + getter.setRequestHeader("Accept-Encoding","identity"); + + boolean get_resp = false; + PageAttributes newattrs = null; + ContentType ctype = null; + byte[] rawdata = null; + try + { // set the Last-Modified date as an If-Modified-Since header on the request + java.util.Date lmod = null; + if (attrs!=null) + lmod = attrs.getLastModified(); + if (lmod!=null) + getter.setRequestHeader("If-Modified-Since",s_httpdate_format.format(lmod)); + + // execute the Get method! + int rc = m_http_client.executeMethod(getter); + get_resp = true; + if ((lmod!=null) && (rc==HttpStatus.SC_NOT_MODIFIED)) + return false; // we were not modified + if (rc==HttpStatus.SC_NO_CONTENT) + return false; // there's no content there + if (rc!=HttpStatus.SC_OK) // this is farked! + throw new TrackbackException("GET of " + url + " returned " + rc); + + // Get the new page attributes and save them off. + newattrs = new PageAttributes(getter); + m_page_cache.put(url,newattrs); + + // Get the Content-Type header and see if it's valid. + Header hdr = getter.getResponseHeader("Content-Type"); + if (hdr!=null) + s = hdr.getValue(); + else + s = "text/plain"; // necessary assumption + ctype = new ContentType(s); + if (!(ctype.getPrimaryType().equals("text"))) + throw new TrackbackException("URL " + url + " does not point to a text-based resource"); + + // Load the resource in as byte data; we will determine the right character set for it later. + rawdata = getter.getResponseBody(); + get_resp = false; + + } // end try + catch (IOException e) + { // IO error getting the page + throw new TrackbackException("I/O error retrieving " + url + ": " + e.getMessage(),e); + + } // end catch + catch (javax.mail.internet.ParseException e) + { // translate into TrackbackException + throw new TrackbackException("invalid Content-Type received for URL " + url,e); + + } // end catch + finally + { // release the connection if possible + if (get_resp) + getter.getResponseBody(); + getter.releaseConnection(); + + } // end finally + + // make a first guess at the charset from the HTTP header Content-Type + String cset = ctype.getParameter("charset"); + if (cset==null) + cset = "US-ASCII"; + String content = null; + try + { // interpret the content + content = new String(rawdata,cset); + + } // end try + catch (UnsupportedEncodingException e) + { // fall back and try just using US-ASCII + cset = null; + try + { // interpret the content + content = new String(rawdata,"US-ASCII"); + + } // end try + catch (UnsupportedEncodingException e2) + { // can't happen + logger.debug("WTF? US-ASCII should damn well be a supported character set!",e2); + + } // end catch + + } // end catch + + // Look for tags in the content. + Map http_attrs = extractHttpEquivTags(content); + + // Try to get a Content-Type attribute from there. + s = (String)(http_attrs.get("CONTENT-TYPE")); + String cset2 = null; + if (s!=null) + { // look for the content type + try + { // parse into Content-Type + ContentType c = new ContentType(s); + if (c.getPrimaryType().equals("text")) + cset2 = c.getParameter("charset"); + + } // end try + catch (javax.mail.internet.ParseException e) + { // can't get a second Content-Type + logger.debug("parse of Content-Type from META tags failed",e); + cset2 = null; + + } // end catch + + } // end if + + if ((cset==null) && (cset2==null)) + throw new TrackbackException("unable to determine character set for " + url); + if ((cset2!=null) && ((cset==null) || !(cset.equalsIgnoreCase(cset2)))) + { // reinterpret content in new character set + try + { // reinterpret content in new character set + s = new String(rawdata,cset2); + content = s; + + // the contents of the HTTP-EQUIV tags may have changed as a result + http_attrs = extractHttpEquivTags(content); + + } // end try + catch (UnsupportedEncodingException e) + { // just use original character set + if (cset==null) + throw new TrackbackException("unable to determine character set for " + url); + + } // end catch + + } // end if + + newattrs.updateFromPage(http_attrs); // update the page attributes from the META tag data + + // Search the page content for RDF blocks. + RE m = new RE(s_rdf_start,RE.MATCH_NORMAL); + int pos = 0; + while (m.match(content,pos)) + { // look for the end of this RDF block + RE m2 = new RE(getEndRecognizer(m.getParen(1)),RE.MATCH_NORMAL); + if (m2.match(content,m.getParenEnd(0))) + { // we now have a block to feed to the XML parser + try + { // run the block through the XML parser + InputSource isrc = new InputSource(new StringReader(content.substring(m.getParenStart(0), + m2.getParenEnd(0)))); + Document doc = m_rdf_parser.parse(isrc); + + // examine topmost element, which should be rdf:RDF + Element root = doc.getDocumentElement(); + if ( NS_RDF.equals(root.getNamespaceURI()) && (root.getLocalName()!=null) + && root.getLocalName().equals("RDF")) + { // this is most definitely an rdf:RDF node...look for rdf:Description nodes under it + NodeList nl = root.getChildNodes(); + for (int i=0; i0)) + item = new URL(s); + s = elt.getAttributeNS(NS_TRACKBACK,"ping"); + if ((s!=null) && (s.length()>0)) + trackback = new URL(s); + if ((item!=null) && (trackback!=null)) + { // create the item + s = elt.getAttributeNS(NS_DC,"title"); + m_item_cache.put(item,new MyTrackbackItem(item,trackback,s,newattrs)); + + } // end if + + } // end try + catch (MalformedURLException e) + { // this means skip this item + logger.warn("URL parse failure",e); + + } // end catch + + } // end if + + } // end for + + } // end if + + } // end try + catch (IOException e) + { // disregard this block + logger.warn("RDF block parse failure",e); + + } // end catch + catch (SAXException e) + { // disregard this block + logger.warn("RDF block parse failure",e); + + } // end catch + + } // end if + // else ignore this possible block + + pos = m.getParenEnd(0); + + } // end while + + return true; + + } // end load + + /** + * Get an item from the items cache under one of two different URLs. + * + * @param url1 First URL to look under. + * @param url2 Second URL to look under. + * @return The item found in the cache, or null if not found. + */ + private synchronized MyTrackbackItem getItem(URL url1, URL url2) + { + MyTrackbackItem rc = (MyTrackbackItem)(m_item_cache.get(url1)); + if (rc==null) + rc = (MyTrackbackItem)(m_item_cache.get(url2)); + return rc; + + } // end getItem + + /*-------------------------------------------------------------------------------- + * External operations + *-------------------------------------------------------------------------------- + */ + + /** + * Given the URL of a trackback item, return the associated + * {@link com.silverwrist.venice.std.TrackbackItem TrackbackItem} object, if it can be found. + * + * @param url The {@link java.net.URL URL} of the trackback item to look for. + * @return The associated TrackbackItem, or null if it could not be found. + * @exception com.silverwrist.venice.except.TrackbackException If there was an error looking for trackback items. + */ + public TrackbackItem getItem(URL url) throws TrackbackException + { + URL normurl = url; + if (url.getRef()!=null) + { // normalize the URL + try + { // we normalize it by chopping at the hashmark + String s = url.toString(); + int n = s.lastIndexOf('#'); + normurl = new URL(s.substring(0,n)); + + } // end try + catch (MalformedURLException e) + { // forget it + normurl = url; + + } // end catch + + } // end if + + MyTrackbackItem rc = getItem(url,normurl); + if ((rc!=null) && rc.expire()) + rc = getItem(url,normurl); // expired - re-get + if (rc!=null) + { // see if we need to reload the item + if (load(url,rc.getAttributes())) + rc = getItem(url,normurl); + + } // end if + else + { // try loading the URL directly + load(url,null); + rc = getItem(url,normurl); + + } // end else + + return rc; + + } // end getItem + + /** + * Scans a specified resource and adds any trackback items found in the page to our cache. + * + * @param url The {@link java.net.URL URL} of the resource to be loaded. + * @exception com.silverwrist.venice.except.TrackbackException If there was an error looking for trackback items. + */ + public void addPage(URL url) throws TrackbackException + { + load(url,null); + + } // end addPage + + /** + * Returns a {@link java.util.Collection Collection} of all + * {@link com.silverwrist.venice.std.TrackbackItem TrackbackItem}s currently in the cache. + * + * @return See above. + */ + public Collection getCachedItems() + { + ArrayList rc = null; + synchronized (this) + { // grab value contents + rc = new ArrayList(m_item_cache.values()); + + } // end synchronized block + + if (rc.isEmpty()) + return Collections.EMPTY_LIST; + else + return Collections.unmodifiableList(rc); + + } // end getCachedItems + + /*-------------------------------------------------------------------------------- + * External static operations + *-------------------------------------------------------------------------------- + */ + + /** + * Return the Singleton instance of TrackbackManager. + * + * @return See above. + */ + public static synchronized TrackbackManager get() + { + if (s_self==null) + s_self = new TrackbackManager(); + return s_self; + + } // end get + + /*-------------------------------------------------------------------------------- + * Static initializer + *-------------------------------------------------------------------------------- + */ + + static + { + // initialize date format for output + DateFormat foo = new SimpleDateFormat(DateParser.PATTERN_RFC1123); + foo.setTimeZone(TimeZone.getTimeZone("GMT")); + s_httpdate_format = foo; + try + { // compile all the regular expressions + s_cache_smaxage = COMPILER.compile("s-maxage\\s*=\\s*(\\d+)"); + s_cache_maxage = COMPILER.compile("max-age\\s*=\\s*(\\d+)"); + s_meta_tag = COMPILER.compile("]*>"); + s_http_equiv_attr = COMPILER.compile("http-equiv\\s*="); + s_content_attr = COMPILER.compile("content\\s*="); + s_rdf_start = COMPILER.compile("<([A-Za-z_][A-Za-z0-9_.-]*):RDF\\s+"); + + } // end try + catch (RESyntaxException e) + { // this is very bad + logger.fatal("Regular expression compile failure",e); + + } // end catch + + } // end static initializer + +} // end class TrackbackManager