Package churchillobjects.rss4j.generator

Source Code of churchillobjects.rss4j.generator.RssGenerator

/*
*  Copyright (c) 1999-2002 ChurchillObjects.com  All rights reserved.
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions are
*  met: Redistributions of source code must retain the above copyright notice,
*  this list of conditions and the following disclaimer. Redistributions in
*  binary form must reproduce the above copyright notice, this list of
*  conditions and the following disclaimer in the documentation and/or other
*  materials provided with the distribution. Neither the name of the copyright
*  holder nor the names of its contributors may be used to endorse or promote
*  products derived from this software without specific prior written
*  permission.
*
*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
*  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
*  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
*  ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
*  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
*  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
*  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
*  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
*  LIABILITY, OR TORT, INCLUDING NEGLIGENCE OR OTHERWISE, ARISING IN ANY WAY
*  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
*  DAMAGE.
*
*/

package churchillobjects.rss4j.generator;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Enumeration;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
//import org.apache.xml.serialize.OutputFormat;
//import org.apache.xml.serialize.XMLSerializer;
import org.w3c.dom.DOMException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
import churchillobjects.rss4j.RssChannel;
import churchillobjects.rss4j.RssDocument;
import churchillobjects.rss4j.RssJbnDependency;
import churchillobjects.rss4j.RssJbnPatch;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;

/**
* A base and factory class for RSS generator objects. From a version number, will
* construct an appropriate implementation and generate the RSS document.
*/
public abstract class RssGenerator {

  /**
   * The DOM implementation used by the XML toolkit (user-supplied, but best
   * to use Apache's Xerces).
   */
  protected DOMImplementation domImpl;

  /**
   * An XML formatter object for the document when it is serialized.
   */
  protected OutputFormat format;

  /**
   * The XML document, used as it is being built.
   */
  protected Document doc;


  protected Writer outputWriter;
  protected OutputStream outputStream;

  protected Element rootElement;

  private boolean truncateText = false;

  // for enforcement of max lengths, used by 0.90 and 0.91, but not 1.0
  protected int channelTitleMax;
  protected int channelDescriptionMax;
  protected int channelLinkMax;
  protected int channelPubDate;
  protected int channelBuildDate;
  protected int channelManagingEditorMax;
  protected int channelWebmasterMax;
  protected int channelCopyrightMax;
  protected int channelDocsMax;
  protected int imageTitleMax;
  protected int imageUrlMax;
  protected int imageLinkMax;
  protected int imageDescriptionMax;
  protected int itemTitleMax;
  protected int itemLinkMax;
  protected int itemDescriptionMax;
  protected int textInputTitleMax;
  protected int textInputDescriptionMax;
  protected int textInputNameMax;
  protected int textInputLinkMax;

  /**
   * Creates an appropriate instance of RssGenerator with a StringWriter and
   * generates the RSS into the writer, then returns the XML as a string.
   * @param document
   * @return
   */
  public static String generateRss(RssDocument document) throws RssGenerationException{
    StringWriter sw = new StringWriter();
    generateRss(document, sw);
    return sw.toString();
  }

  /**
   * Creates an appropriate instance of RssGenerator and has it write the
   * RSS code to the specified output stream.
   * @param document
   * @param output
   * @throws RssGenerationException
   */
  public static void generateRss(RssDocument document, OutputStream output) throws RssGenerationException{
    RssGenerator generator = getGenerator(document);
    generator.writeRssDocument(document, output);
  }

  /**
   * Creates an appropriate instance of RssGenerator and has it write the
   * RSS code to the specified file object.
   * @param document
   * @param file
   * @throws RssGenerationException
   */
  public static void generateRss(RssDocument document, File file) throws RssGenerationException{
    RssGenerator generator = getGenerator(document);
    generator.writeRssDocument(document, file);
  }

  /**
   * Creates an appropriate instance of RssGenerator and has it write the
   * RSS code to the specified output writer.
   * @param document
   * @param output
   * @throws RssGenerationException
   */
  public static void generateRss(RssDocument document, Writer output) throws RssGenerationException{
    RssGenerator generator = getGenerator(document);
    generator.writeRssDocument(document, output);
  }


  /**
   * Returns the appropriate generator for the specified RSS document object.
   * If none, then a RSSGenerationException is thrown.
   * @param document
   * @return
   */
  private static RssGenerator getGenerator(RssDocument document) throws RssGenerationException{
    String version = document.getVersion();
    if(version==null || version.length()==0){
      throw new RssGenerationException("RSS version was not specified in the RssDocument object.");
    }
    if("0.90".equals(version)){
      return new RssGeneratorImpl090();
    }
    else if("0.91".equals(version)){
      return new RssGeneratorImpl091();
    }
    else if("1.0".equals(version)){
      return new RssGeneratorImpl100();
    }
    throw new RssGenerationException("Could not find a generator for the document version: " + version);
  }

  /**
   * Constructor. Sets up the XML document
   * and formatter, then invokes setMaxLengths.
   */
  RssGenerator() throws RssGenerationException{
    try{
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setNamespaceAware(true);
      DocumentBuilder builder = factory.newDocumentBuilder();
      domImpl = builder.getDOMImplementation();
      format = new OutputFormat();
      format.setLineWidth(65);
      format.setIndenting(true);
      format.setIndent(2);
      format.setEncoding("UTF-8");
      format.setMediaType("application/xml");
      format.setOmitComments(true);
      format.setOmitXMLDeclaration(false);
      format.setVersion("1.0");
      format.setStandalone(true);
    }
    catch (ParserConfigurationException e) {
      throw new RssGenerationException("Could not locate a JAXP DocumentBuilder class");
    }
    setMaxLengths();
  }

  /**
   * Implemented by subclasses. If there are field length limitations that need
   * to be adhered to, then the version implementation would set them here and
   * it will be invoked at initialization time.
   */
  protected abstract void setMaxLengths();

  protected abstract void handleChannel(RssChannel channel) throws RssGenerationException;

  protected abstract void finishDocument() throws RssGenerationException;

  /**
   * Serializes the completed DOM structure to the source given by the client code.
   * Any problems here are wrapped in an RssGenerationException and thrown up the
   * stack.
   * @param data
   * @throws RssGenerationException
   */
  private void writeRssDocument(RssDocument data) throws RssGenerationException{
    try{
      createRssDocument(data);
      Enumeration channels = data.channels();
      while(channels.hasMoreElements()){
        RssChannel channel = (RssChannel)channels.nextElement();
        if(channel!=null){
          handleChannel(channel);
        }
      }
      finishDocument();

      XMLSerializer serializer = null;

      if(outputWriter!=null){
        serializer = new XMLSerializer(outputWriter, format);
      }
      else{
        serializer = new XMLSerializer(outputStream, format);
      }
      serializer.serialize(doc);
    }
    catch (FactoryConfigurationError e) {
      throw new RssGenerationException("Could not locate a JAXP factory class");
    }
    catch (DOMException e) {
      throw new RssGenerationException(e);
    }
    catch (IOException e) {
      throw new RssGenerationException(e);
    }
  }

  /**
   * Subclass implementation for creating the DOM from the specified RSS document
   * object model.
   * @param data
   * @throws RssGenerationException
   */
  protected abstract void createRssDocument(RssDocument data) throws RssGenerationException;


  /**
   * Overloaded write method fo a Java IO output stream. The resulting RSS code
   * will be written to the specified output stream.
   * @param data
   * @param output
   * @throws RssGenerationException
   */
  private void writeRssDocument(RssDocument data, OutputStream output) throws RssGenerationException{
    outputStream = output;
    writeRssDocument(data);
  }


  /**
   * Overloaded write method for a Java File object. The resulting RSS code will be
   * written to the specified file.
   * @param data
   * @param file
   * @throws RssGenerationException
   */
  private void writeRssDocument(RssDocument data, File file) throws RssGenerationException{
    try{
      outputStream = new FileOutputStream(file);
      writeRssDocument(data);
    }
    catch(IOException e){
      throw new RssGenerationException(e);
    }
  }

  /**
   * Overloaded write method for a Java IO writer object. The resulting RSS code
   * will be written to the specified writer.
   * @param data
   * @param output
   * @throws RssGenerationException
   */
  private void writeRssDocument(RssDocument data, Writer output) throws RssGenerationException{
    outputWriter = output;
    writeRssDocument(data);
  }

  /**
   * Convenience method that adds a new node to an existing XML element with
   * the specified value. The method will not create and add the new node if
   * the value is null.
   * @param baseElement
   * @param nodeName
   * @param textValue
   * @return
   */
  protected Element add(Element baseElement, String nodeName, String textValue){
    if(textValue!=null){
      Element attrib = doc.createElement(nodeName);
      Text chars = doc.createTextNode(textValue);
      attrib.appendChild(chars);
      baseElement.appendChild(attrib);
      return attrib;
    }
    return null;
  }

    /**
     * Convenience method that adds a new node with a child node to an existing XML element with the specified values.
     * The method will not create and add the new node if the innerNodeValues AND the attributeValue is null.<br><br>
     * &lt;outerNodeName attributeName=attributeValue &gt;<br>
     * &nbsp;&nbsp;&lt; nodeName &gt; nodeValue1 &lt;/nodeName &gt;
     * &nbsp;&nbsp;&lt; nodeName &gt; nodeValue2 &lt;/nodeName &gt;
     * &lt;/outerNodeName &gt;
     * @param baseElement the base element that everything is added to
     * @param outerNodeName the name for the outer node
     * @param attributeName the name for the attribute of the outer node
     * @param attributeValue the value for the attribute of the inner node
     * @param innerNodeName the name for the inner node
     * @param innerNodeValues the value(s) for the inner nodes.
     * @return the base element
     */
    protected Element add( Element baseElement, String outerNodeName, String attributeName, String attributeValue,
                           String innerNodeName, Collection innerNodeValues)
    {
        if( attributeValue != null || innerNodeValues != null )
        {
            Element outerNode = doc.createElement( outerNodeName );
            if( attributeValue != null )
            {
                outerNode.setAttribute( attributeName, attributeValue);
            }

            if( innerNodeValues != null )
            {
                Iterator innerValues = innerNodeValues.iterator();
                while( innerValues.hasNext() )
                {
                    Element innerNode = doc.createElement( innerNodeName );
                    Text chars = doc.createTextNode( (String)innerValues.next() );
                    innerNode.appendChild( chars );
                    outerNode.appendChild( innerNode );
                }
            }
            baseElement.appendChild( outerNode );
        }

        return baseElement;
    }

    /**
     * Convenience method that adds a new node with a child node to an existing XML element with the specified values.
     * The method will not create and add the new node if the innerNodeValues AND the attributeValue is null.<br><br>
     * &lt;outerNodeName  &gt;<br>
     * &nbsp;&nbsp;&lt; innerNodeName innerNodeAttributeName = dependencies[0].geUrl() &gt; dependencies[0].getName() &lt;/innerNodeName &gt;<br>
     * &nbsp;&nbsp;&lt; innerNodeName innerNodeAttributeName = dependencies[1].geUrl() &gt; dependencies[1].getName() &lt;/innerNodeName &gt;<br>
     * &lt;/ outerNodeName &gt;<br>
     * <br>ex:<br>
     * &lt;jbn:products&gt;<br>
     * &nbsp;&nbsp;&lt; jbn:product rdf:about=http://network.jboss.com?id=123 &gt;JBossAS 4.0.2 &gt;<br>
     * &nbsp;&nbsp;&lt; jbn:product rdf:about=http://network.jboss.com?id=456 &gt;JBossAS 4.0.3 &gt;<br>
     * &lt;/ jbn:producs&gt;<br>
     *
     * @param baseElement the base element that everything is added to
     * @param outerNodeName the name for the outer node
     * @param innerNodeName the name for the inner node
     * @param innerNodeAttributeName the name for the attribute of the inner node
     * @param dependencies the dependency.getUrl will be the attribute Value and the dependency.getName will be the node value
     * @return
     */
    protected Element add( Element baseElement, String outerNodeName, String innerNodeName,
                           String innerNodeAttributeName, Collection dependencies )
    {

        //TODO add tests for this method
        //    -- check for nulls
        //    -- make sure exception is thrown correctly
        //    -- verify various correct formats are produced (with and without nulls)
        if( innerNodeName != null &&  dependencies != null )
        {
            Element outerNode = doc.createElement( outerNodeName );

                Iterator iterator = dependencies.iterator();

                while( iterator.hasNext() )
                {
                    Element innerNode = doc.createElement( innerNodeName );
                    RssJbnDependency dependency = (RssJbnDependency)iterator.next();
                    if( innerNodeAttributeName != null && dependency != null )
                    {
                        innerNode.setAttribute( innerNodeAttributeName, dependency.getUrl() );
                    }
                    Text chars = doc.createTextNode( dependency.getName() );
                    innerNode.appendChild( chars );
                    outerNode.appendChild( innerNode );
                }

            baseElement.appendChild( outerNode );
        }
        return baseElement;
    }

    /**
     * this add method expects a collection of RssJbnDependency objects -- that have attributes for a jbn:product
     * @param baseElement the base element this node will be added to
     * @param outerNodeName the name for the outer node (should be JBN:PRODUCTS)
     * @param innerNodeName the name for the inner node (should be JBN:PRODUCT)
     * @param dependencies a collection of RssJbnDependency objects
     * @return the base element
     */
    protected Element add( Element baseElement, String outerNodeName, String innerNodeName, Collection dependencies )
    {
        Element outerNode = doc.createElement( outerNodeName );

        if( innerNodeName != null &&  dependencies != null )
        {
            Iterator dependencyIterator = dependencies.iterator();

            while( dependencyIterator.hasNext() )
            {
                RssJbnDependency rjd = (RssJbnDependency)dependencyIterator.next();
                Map nameValuePair = new HashMap();
                nameValuePair.put( "rdf:about", rjd.getUrl() );
                nameValuePair.put( RssJbnPatch.PREFIX + ":" + RssJbnDependency.ATTR_PRODUCT_NAME, rjd.getProductName() );
                nameValuePair.put( RssJbnPatch.PREFIX + ":" + RssJbnDependency.ATTR_PRODUCT_VERSION, rjd.getProductVersion() );
                nameValuePair.put( RssJbnPatch.PREFIX + ":" + RssJbnDependency.ATTR_JON_RESOURCE_TYPE, rjd.getJonResourceType() );
                nameValuePair.put( RssJbnPatch.PREFIX + ":" + RssJbnDependency.ATTR_JON_RESOURCE_VERSION, rjd.getJonResourceVersion() );
                outerNode.appendChild(
                        add( RssJbnPatch.PREFIX + ":" + RssJbnPatch.ATTR_PRODUCT,
                                nameValuePair, rjd.getName() ) );
            }
            baseElement.appendChild( outerNode );
        }

        return baseElement;

    }

    /**
     * creates an inner node based on the attribute name/value pair.  This method is originally meant for the JBN:PRODUCT element
     * @param innerNodeName the name of the inner node
     * @param attributeNameValuePairs a HashMap of attribute name/value pairs
     * @param content the content of the element
     * @return the new inner node
     */
    private Element add( String innerNodeName, Map attributeNameValuePairs, String content )
    {
        Element innerNode = doc.createElement( innerNodeName );
        Iterator keyIterator = attributeNameValuePairs.keySet().iterator();
        while( keyIterator.hasNext() )
        {
            String name = (String)keyIterator.next();
            String value = (String)attributeNameValuePairs.get( name );
            innerNode.setAttribute( name, value );
        }
        Text chars = doc.createTextNode( content );
        innerNode.appendChild( chars );
        return innerNode;
    }

    /**
   * Set this accessor to TRUE if truncation of text is desired. This will truncate
   * any text that is truncatable (titles, descriptions, etc) and append an ellipsis
   * (three dots) at the end of the text to show that there is more. This only
   * applies if the text would otherwise exceed the limit of the field during
   * generation (and thus will not change the object that contains the too-long
   * text). Applies to RSS 0.90 and 0.91 specifications.
   * @param b Truncate too-long text during RSS generation, if the standard warrants it.
   */
  public void setTruncateText(boolean b){
    truncateText = b;
  }

  /**
   * Inidcates that truncation is on or off, whereby certain text (titles, descriptions,
   * etc. will be truncated if they exceed the RSS standard being used.
   * @return If text will be truncated if it happens to be too long for the standard.
   */
  public boolean isTruncateText(){
    return truncateText;
  }

  /**
   *
   * @param s The string value to truncate. If null, then nothing is done.
   * @param length The number of characters to truncate to. If zero, then there is
   * no limit on the length of the element.
   * @return The truncated (if necessary) string, or null if s is null.
   * @throws RssGenerationException
   */
  protected String truncate(String s, int length) throws RssGenerationException{
    if(!truncateText || s==null || length==0){
      return s;
    }
    s = s.trim();
    if(s.length() <= length){
      return s;
    }
    s = s.substring(0,length-3).trim();
    return s+"...";
  }



  /**
   * Validates a value for a field that is required, checking its minimum and
   * maximum lengths. If the value is null, has no length or exceeds the required
   * length (if any), then an RssGenerationException is thrown up the stack.
   * the test passes.
   * @param value
   * @param name
   * @param minLength
   * @param maxLength
   * @throws RssGenerationException
   */
  protected void validateValueRequired(String value, String name, int minLength, int maxLength) throws RssGenerationException{
    if(value==null || value.length()==0){
      throw new RssGenerationException(name + " attribute required");
    }
    if(value==null || value.length()==0 || (maxLength>0 && value.length()>maxLength)){
      throw new RssGenerationException(name + " attribute must be between " + minLength + " and " + maxLength + " characters: " + value.length() + " '" + value + "'");
    }
  }

  /**
   * Overloaded version of validateValueRequired, for when there is no minimum
   * length for the field (1 will be used for minimum length).
   * @param value
   * @param name
   * @param maxLength
   * @throws RssGenerationException
   */
  protected void validateValueRequired(String value, String name, int maxLength) throws RssGenerationException{
    validateValueRequired(value, name, 1, maxLength);
  }

  /**
   * Validates a value for a field that is not required, checking its minimum and
   * maximum lengths. If the value is null or there is no maximum (not > 0), then
   * the test passes.
   * @param value
   * @param name
   * @param minLength
   * @param maxLength
   * @throws RssGenerationException
   */
  protected void validateValueOptional(String value, String name, int minLength, int maxLength) throws RssGenerationException{
    if(maxLength>0 && value!=null && value.length()>maxLength){
      throw new RssGenerationException(name + " optional, but length must be between " +
          minLength + " and " + maxLength + " characters: " + value.length() + " '" + value + "'");
    }
  }

  /**
   * Overloaded version of validateValueOptional, for when there is no minimum
   * length for the field (1 will be used for minimum length).
   * @param value
   * @param name
   * @param maxLength
   * @throws RssGenerationException
   */
  protected void validateValueOptional(String value, String name, int maxLength) throws RssGenerationException{
    validateValueOptional(value, name, 1, maxLength);
  }

  /**
   * For fields that may have HTML embedded in the code, adds a CDATA wrapper for
   * the text value so that XML validators will not blow up.
   * @param s The text to be evaluated
   * @return The modified text, or original if not modified
   */
  protected String embedHtml(String s){
    if(s.indexOf("<")>=0){
      s = "<[!CDATA[" + s + "]]>";
    }
    return s;
  }

  /**
   * For fields that must have valid URIs, checks to see that the field contains one of
   * the four valid URI prefixes (http, https, ftp or mailto).
   * @param s The URI value to evaluate
   * @throws RssGenerationException When the validation fails
   */
  protected void validateUri(String s) throws RssGenerationException{
    if(s!=null && s.length()>0 && !(
            s.startsWith("http:") ||
            s.startsWith("https:") ||
            s.startsWith("ftp:") ||
            s.startsWith("mailto:")
            )){
      throw new RssGenerationException(
          "Invalid URI format: must be 'http:', 'https:', 'ftp:', or 'mailto:' - " + s);
    }

  }

}
TOP

Related Classes of churchillobjects.rss4j.generator.RssGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.