de.mpg.escidoc.services.syndication.feed.Feed.java Source code

Java tutorial

Introduction

Here is the source code for de.mpg.escidoc.services.syndication.feed.Feed.java

Source

/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at license/ESCIDOC.LICENSE
* or http://www.escidoc.org/license.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at license/ESCIDOC.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/

/*
* Copyright 2006-2012 Fachinformationszentrum Karlsruhe Gesellschaft
* fr wissenschaftlich-technische Information mbH and Max-Planck-
* Gesellschaft zur Frderung der Wissenschaft e.V.
* All rights reserved. Use is subject to license terms.
*/

/**
 * Feed class for eSciDoc syndication manager.  
 *    
 * @author Vlad Makarenko  (initial creation) 
 * @author $Author$ (last modification)
 * $Revision$
 * $LastChangedDate$ 
 */

package de.mpg.escidoc.services.syndication.feed;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.log4j.Logger;

import com.sun.syndication.feed.atom.Content;
import com.sun.syndication.feed.module.mediarss.MediaEntryModuleImpl;
import com.sun.syndication.feed.module.mediarss.types.MediaContent;
import com.sun.syndication.feed.module.mediarss.types.Metadata;
import com.sun.syndication.feed.module.mediarss.types.Thumbnail;
import com.sun.syndication.feed.module.mediarss.types.UrlReference;
import com.sun.syndication.feed.synd.SyndCategory;
import com.sun.syndication.feed.synd.SyndCategoryImpl;
import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.sun.syndication.feed.synd.SyndFeedImpl;
import com.sun.syndication.feed.synd.SyndImage;
import com.sun.syndication.feed.synd.SyndPerson;
import com.sun.syndication.feed.synd.SyndPersonImpl;
import com.sun.syndication.io.FeedException;

import de.mpg.escidoc.services.common.XmlTransforming;
import de.mpg.escidoc.services.common.exceptions.TechnicalException;
import de.mpg.escidoc.services.common.util.HtmlUtils;
import de.mpg.escidoc.services.common.valueobjects.ItemVO;
import de.mpg.escidoc.services.common.valueobjects.metadata.CreatorVO;
import de.mpg.escidoc.services.common.valueobjects.metadata.TextVO;
import de.mpg.escidoc.services.common.valueobjects.publication.MdsPublicationVO;
import de.mpg.escidoc.services.common.valueobjects.publication.PubItemVO;
import de.mpg.escidoc.services.common.xmltransforming.XmlTransformingBean;
import de.mpg.escidoc.services.framework.PropertyReader;
import de.mpg.escidoc.services.framework.ProxyHelper;
import de.mpg.escidoc.services.syndication.SyndicationException;
import de.mpg.escidoc.services.syndication.Utils;

public class Feed extends SyndFeedImpl {

    private static final long serialVersionUID = 1L;
    private static final String FEEDS_CONTENT_MODEL = "escidoc.framework_access.content-model.id.publication";

    private static final Logger logger = Logger.getLogger(Feed.class);

    /** EJB instance of search service. */
    //    private Search itemContainerSearch = new SearchBeanLocal();
    //    @EJB
    //    private Search itemContainerSearch;

    private static final String CDATA = "<![CDATA[%s]]>";

    //Search CQL query 
    //see: http://www.escidoc-project.de/documentation/Soap_api_doc_SB_Search.pdf
    private String query;

    //Sort keys 
    private String sortKeys;

    //Records limit
    private String maximumRecords;

    //List of the all available types for the feed
    private String feedTypes;

    //template for html rel link generation
    private String relLink;

    //uriMatcher is RegExp for URI matching
    private String uriMatcher;

    //use caching if "true", do not use, otherwise
    private String cachingStatus;

    //TTL for until the channel, it will be recached after  
    private String cachingTtl = "0";

    //List of the parameters generated according to the URI
    private List paramList = new ArrayList<String>();

    //Hash of the parameters/values
    private Map<String, String> paramHash = new HashMap<String, String>();

    //XML transformation bean
    private static XmlTransforming xt = new XmlTransformingBean();

    /**
     * Query getter.
     * @return <code>query</code>
     */
    public String getQuery() {
        return query;
    }

    /**
     * Query setter. 
     * @param query
     */
    public void setQuery(String query) {
        try {
            if (query != null && query.contains("${content_model}")) {
                String contentModel = PropertyReader.getProperty(FEEDS_CONTENT_MODEL);
                if (contentModel != null) {
                    query = query.replaceAll("\\$\\{content_model\\}", contentModel);
                }
            }
        } catch (IOException e) {
            System.out.println("Problem reading property(" + FEEDS_CONTENT_MODEL + ")");
            e.printStackTrace();
        } catch (URISyntaxException e) {
            System.out.println("Problem replacing ${content_model} with specific content-model in setQuery");
            e.printStackTrace();
        }
        this.query = query;
    }

    /**
     * SortKeys getter
     * @return <code>sortKeys</code>
     */
    public String getSortKeys() {
        return sortKeys;
    }

    /**
     * SortKeys setter.
     * @param sortKeys
     */
    public void setSortKeys(String sortKeys) {
        this.sortKeys = sortKeys;
    }

    /**
     * MaximumRecords getter.
     * @return <code>MaximumRecords</code>
     */
    public String getMaximumRecords() {
        return maximumRecords;
    }

    /**
     * MaximumRecords setter.
     * @param maximumRecords
     */
    public void setMaximumRecords(String maximumRecords) {
        this.maximumRecords = maximumRecords;
    }

    /**
     * FeedTypes getter.
     * @return <code>FeedTypes</code>
     */
    public String getFeedTypes() {
        return feedTypes;
    }

    /**
     * FeedTypes setter.
     * @param feedTypes
     */
    public void setFeedTypes(String feedTypes) {
        this.feedTypes = feedTypes;
    }

    /**
     * UriMatcher getter.
     * @return <code>UriMatcher</code>
     */
    public String getUriMatcher() {
        return uriMatcher;
    }

    /**
     * UriMatcher setter.
     * @param uriMatcher
     */
    public void setUriMatcher(String uriMatcher) {
        this.uriMatcher = uriMatcher;
    }

    public String getRelLink() {
        return relLink;
    }

    public void setRelLink(String relLink) {
        this.relLink = relLink;
    }

    public String generateRelLink(String uri) throws SyndicationException {

        populateParamsFromUri(uri);

        String feedLink = "<link href=\"" + StringEscapeUtils.escapeXml(uri) + "\"" + " rel=\"alternate\" type=\""
                + getFeedMimeType((String) paramHash.get("${feedType}")) + "\" title=\""
                + StringEscapeUtils.escapeXml(populateFieldWithParams("relLink", getRelLink())) + "\" />\n";

        return feedLink;
    }

    private String getFeedMimeType(String feedType) throws SyndicationException {
        Utils.checkName(feedType, "feedType is empty");
        return "application/" + (feedType.toLowerCase().contains("atom") ? "atom" : "rss") + "+xml";
    }

    /**
     * CachingStatus getter.
     * @return <code>CachingStatus</code>
     */
    public String getCachingStatus() {
        return cachingStatus;
    }

    /**
     * CachingStatus setter.
     * @param status
     */
    public void setCachingStatus(String status) {
        this.cachingStatus = status;
    }

    /**
     * CachingTtl getter.
     * @return <code>CachingTtl</code>
     */
    public String getCachingTtl() {
        return cachingTtl;
    }

    /**
     * CachingTtl setter.
     * @param cachingTtl
     */
    public void setCachingTtl(String cachingTtl) {
        this.cachingTtl = cachingTtl;
    }

    /**
     * ParamList getter
     * @return <code>ParamList</code>
     */
    public List getParamList() {
        return paramList;
    }

    /**
     * ParamList setter.
     * @param paramList
     */
    public void setParamList(List paramList) {
        this.paramList = paramList;
    }

    /**
     * Add new SyndCategory. 
     * @param sc  
     */
    public void addCategory(SyndCategory sc) {
        getCategories().add(sc);
    }

    /**
     * Add new SyndPerson as Author.
     * @param sp
     */
    public void addAuthor(SyndPerson sp) {
        getAuthors().add(sp);
    }

    /* (non-Javadoc)
     * @see com.sun.syndication.feed.synd.SyndFeedImpl#setUri(java.lang.String)
     */
    @Override
    public void setUri(String uri) {
        super.setUri(uri);
        generateUriMatcher(uri);
    }

    /* (non-Javadoc)
     * @see com.sun.syndication.feed.synd.SyndFeedImpl#clone()
     */
    @Override
    public Object clone() {
        Object clone = null;
        try {
            clone = super.clone();
        } catch (CloneNotSupportedException e) {
            // should never happen
        }
        return clone;
    }

    /**
     * Generate <code>UriMatcher</code> for the feed and list of the parameters <code>paramList</code>  
     * according to the <code>uri</code>. 
     * @param uri
     */
    public void generateUriMatcher(final String uri) {
        String result = new String(uri);

        result = escapeUri(result);

        //property regexp in uri
        String regexp = "(\\$\\{[\\w.]+?\\})";

        Matcher m = Pattern.compile(regexp, Pattern.CASE_INSENSITIVE | Pattern.DOTALL).matcher(uri);
        while (m.find()) {
            String param = m.group(1);
            paramList.add(param);
            result = result.replaceFirst(Utils.quoteReplacement(param), "\\(.+\\)?");
        }
        setUriMatcher(result);
    }

    private String escapeUri(String uri) {
        return uri.replaceAll("\\?", "\\\\?");
    }

    /**
     * Populate parameters with the values taken from the certain <code>uri</code> 
     * and populate <code>paramHash</code> with the parameter/value paars.
     * @param uri
     * @throws SyndicationException
     */
    private void populateParamsFromUri(String uri) throws SyndicationException {
        Utils.checkName(uri, "Uri is empty");

        String um = getUriMatcher();

        Utils.checkName(um, "Uri matcher is empty");

        Matcher m = Pattern.compile(um, Pattern.CASE_INSENSITIVE | Pattern.DOTALL).matcher(uri);
        if (m.find()) {
            for (int i = 0; i < m.groupCount(); i++)
                paramHash.put((String) paramList.get(i), m.group(i + 1));
        }

        //special handling of Organizational Unit Feed
        //TODO: should be resolved other way!
        if (getUriMatcher().equals("(.+)?/syndication/feed/(.+)?/publications/organization/(.+)?")) {
            TreeMap<String, String> outm = Utils.getOrganizationUnitTree();
            String oid = (String) paramHash.get("${organizationId}");
            for (Map.Entry<String, String> entry : outm.entrySet()) {
                if (entry.getValue().equals(oid)) {
                    paramHash.put("${organizationName}", entry.getKey());
                }
            }
        }

        logger.info("parameters: " + paramHash);

    }

    /**
     * Populate feed/channel element with the parameters from the <code>paramHash</code>  
     * @param name - the name of the element, is only needed for check <code>value</code> message    
     * @param value is value of the the element to be populated 
     * @return populated <code>value</code>
     * @throws SyndicationException
     */
    public String populateFieldWithParams(String name, String value) throws SyndicationException {
        Utils.checkName(value, "Field <" + name + "> is empty");

        Utils.checkCondition(paramHash.keySet().size() == 0, "No parameters for Uri matcher");
        for (String key : (Set<String>) paramHash.keySet()) {
            value = value.replaceAll(Utils.quoteReplacement(key), (String) paramHash.get(key));
        }
        return value;
    }

    /**
     * List of feed/channel elements to be populated by @see #populateFieldWithParams(String, String)
     * @throws SyndicationException
     */
    private void populateFeedElementsWithParams() throws SyndicationException {

        setTitle(populateFieldWithParams("title", getTitle()));
        setDescription(populateFieldWithParams("description", getDescription()));

        setLink(populateFieldWithParams("link", getLink()));
        setUri(populateFieldWithParams("uri", getUri()));

        setRelLink(populateFieldWithParams("relLink", getRelLink()));

        setQuery(populateFieldWithParams("query", getQuery()));

        SyndImage si = getImage();
        si.setLink(populateFieldWithParams("image/link", si.getLink()));
        si.setUrl(populateFieldWithParams("image/url", si.getUrl()));
        setImage(si);

    }

    public String toString() {

        String str = "[" + "query: " + getQuery() + "\n" + "feedTypes: " + getFeedTypes() + "\n" + "uriMatcher: "
                + getUriMatcher() + "\n" + "paramList: " + paramList + "\n" + "cachingStatus: " + cachingStatus
                + "\n" + "cachingTtl: " + cachingTtl + "\n" + super.toString() + "]";

        return str;
    }

    /**
     * Main method for entries population of the feed/channel according to the <code>uri</code> 
     * @param uri is URI for feed parameters
     * @throws SyndicationException
     */
    public void populateEntries(String uri) throws SyndicationException {

        logger.info("uri:" + uri);

        populateParamsFromUri(uri);

        //set feedType
        String ft = (String) paramHash.get("${feedType}");
        if (!Utils.findInList(getFeedTypes().split(","), ft)) {
            throw new SyndicationException("Requested feed type: " + ft + " is not supported");
        }
        setFeedType(ft);

        populateFeedElementsWithParams();

        setChannelLimitations();

        //search for itemList

        String itemListXml = null;
        /*
        //hack to test Faces
        if ( getQuery().equals("escidoc.content-model.objid=escidoc:faces40 and escidoc.property.public-status=released") )
        {
           try {
        itemListXml = Utils.getResourceAsString("src/test/resources/FacesExport.xml");
           } catch (IOException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
           }
        }
        else
        {*/
        itemListXml = performSearch(getQuery(), getMaximumRecords(), getSortKeys());
        /*}*/

        //populate entires
        setEntries(transformToEntryList(itemListXml));

        //Set PublishedDate of the feed to the PublishedDate of the latest  Entry
        //or, if no entires presented, to the current date
        List e = getEntries();
        setPublishedDate(Utils.checkList(e) ? ((SyndEntry) e.get(0)).getPublishedDate() : new Date());

    }

    /**
     * Search for the items for feed entries population. 
     * The HTTP request to the SearchAndExport WEB interface is used 
     * @param query is CQL query.
     * @param maximumRecords is the limit of the search 
     * @param sortKeys 
     * @return item list XML
     * @throws SyndicationException
     */
    private String performSearch(String query, String maximumRecords, String sortKeys) throws SyndicationException {
        URL url;
        try {
            url = new URL(paramHash.get("${baseUrl}") + "/search/SearchAndExport?" + "cqlQuery="
                    + URLEncoder.encode(query, "UTF-8") + "&maximumRecords="
                    + URLEncoder.encode(maximumRecords, "UTF-8") + "&sortKeys="
                    + URLEncoder.encode(sortKeys, "UTF-8") + "&exportFormat=ESCIDOC_XML_V13"
                    + "&sortOrder=descending" + "&language=all");
        } catch (Exception e) {
            throw new SyndicationException("Wrong URL:", e);
        }

        Object content;
        URLConnection uconn;

        try {
            uconn = ProxyHelper.openConnection(url);
            if (!(uconn instanceof HttpURLConnection))
                throw new IllegalArgumentException("URL protocol must be HTTP.");
            HttpURLConnection conn = (HttpURLConnection) uconn;

            InputStream stream = conn.getErrorStream();
            if (stream != null) {
                conn.disconnect();
                throw new SyndicationException(Utils.getInputStreamAsString(stream));
            } else if ((content = conn.getContent()) != null && content instanceof InputStream)
                content = Utils.getInputStreamAsString((InputStream) content);
            else {
                conn.disconnect();
                throw new SyndicationException("Cannot retrieve content from the HTTP response");
            }
            conn.disconnect();

            return (String) content;
        } catch (Exception e) {
            throw new SyndicationException(e);
        }

    }

    /**
     * Transformation method takes ItemList XML and transforms it to the list of 
     * syndication entries (<code><List>SyndEntry</code>)  
     * @param itemListXml 
     * @return <List>SyndEntry
     * @throws SyndicationException
     */
    private List transformToEntryList(String itemListXml) throws SyndicationException {

        List entries = new ArrayList();

        List<ItemVO> itemListVO = null;
        try {
            itemListVO = (List<ItemVO>) xt.transformToItemList(itemListXml);
        } catch (Exception e) {
            throw new SyndicationException("Cannot transform item list XML to List<ItemVO>:", e);
        }

        for (ItemVO item : itemListVO) {

            SyndEntry se = new SyndEntryImpl();

            PubItemVO pi = (PubItemVO) item;
            MdsPublicationVO md = pi.getMetadata();

            //Content

            /*
            SyndContent scont = new SyndContentImpl();
            scont.setType("application/xml");
            try {
               String itemXml = replaceXmlHeader(xt.transformToItem( pi ));
                
               scont.setValue( itemXml );
               if ( "atom_0.3".equals(getFeedType()) )
                  scont.setMode(Content.XML);
            } 
            catch (TechnicalException e) 
            {
               throw new RuntimeException("Cannot transform to XML: ", e);
            }; 
                
            se.setContents(Arrays.asList(scont));
            */
            //
            se.setTitle(HtmlUtils.removeSubSupIfBalanced(md.getTitle().getValue()));

            //Description ??? optional
            List abs = md.getAbstracts();
            SyndContent sc = new SyndContentImpl();
            sc.setValue(Utils.checkList(abs) ? ((TextVO) abs.get(0)).getValue() : null);
            se.setDescription(sc);

            //Category
            TextVO subj = md.getFreeKeywords();
            if (subj != null && Utils.checkVal(subj.getValue())) {
                List<SyndCategory> categories = new ArrayList<SyndCategory>();
                SyndCategory scat = new SyndCategoryImpl();
                scat.setName(subj.getValue());
                categories.add(scat);
                se.setCategories(categories);
            }

            if (Utils.checkList(md.getCreators())) {
                List<SyndPerson> authors = new ArrayList<SyndPerson>();
                SyndPerson sp;
                StringBuffer allCrs = new StringBuffer();
                int counter = 0;
                for (CreatorVO creator : md.getCreators()) {

                    String crs = creator.getPerson() != null
                            ? Utils.join(Arrays.asList(creator.getPerson().getFamilyName(),
                                    creator.getPerson().getGivenName()), ", ")
                            : creator.getOrganization().getName().getValue();

                    sp = new SyndPersonImpl();
                    sp.setName(crs);
                    authors.add(sp);

                    allCrs.append(crs);
                    if (counter + 1 != md.getCreators().size()) {
                        allCrs.append("; ");
                    }

                    counter++;

                }

                se.setAuthor(allCrs.toString());
                se.setAuthors(authors);
                //            se.setContributors(contributors);
            }

            //Contents ???
            //se.setContents(contents)

            se.setLink(
                    paramHash.get("${baseUrl}") + "/pubman/item/" + pi.getLatestRelease().getObjectIdAndVersion());

            //Uri ????
            se.setUri(se.getLink());

            //Entry UpdatedDate ??? 
            se.setUpdatedDate(pi.getModificationDate());

            //Entry PublishedDate ???
            se.setPublishedDate(pi.getLatestRelease().getModificationDate());

            setEntryLimitations(se);

            populateModules(se, md);

            entries.add(se);

        }

        return entries;

    }

    /**
     * @param se
     * @param md
     * @throws FeedException
     */
    private void populateModules(SyndEntry se, MdsPublicationVO md) throws SyndicationException {
        //populateMediaRss(se, md);

    }

    /**
     * @param se
     * @param pubMd
     * @throws FeedException
     */
    private void populateMediaRss(SyndEntry se, MdsPublicationVO pubMd) throws SyndicationException {

        try {
            MediaContent[] contents = new MediaContent[1];
            MediaContent myItem = new MediaContent(new UrlReference("http://me.com/movie.mpg"));
            contents[0] = myItem;
            Metadata md = new Metadata();
            Thumbnail[] thumbs = new Thumbnail[2];
            thumbs[0] = new Thumbnail(new URL("http://me.com/movie1.jpg"));
            thumbs[1] = new Thumbnail(new URL("http://me.com/movie2.jpg"));
            md.setThumbnail(thumbs);
            myItem.setMetadata(md);
            MediaEntryModuleImpl module = new MediaEntryModuleImpl();
            module.setMediaContents(contents);
            se.getModules().add(module);

            //            logger.info( se.getModule( MediaModule.URI ) );

        } catch (Exception e) {
            throw new SyndicationException("Problem by MediaRss module:", e);
        }

    }

    /**
     * set channel limitations
     */
    private void setChannelLimitations() {

        //set setMaximumRecords to 15 for RSS 0.9, 0.91N, 091U 
        if (isRSS_09_or_091N_or_091U() && Integer.parseInt(getMaximumRecords()) > 15)
            setMaximumRecords("15");

        //length of the channel/image title of RSS 0.9  <= 40 
        if (getFeedType().equals("rss_0.9")) {
            setTitle(Utils.cutString(getTitle(), 40, "..."));
            getImage().setTitle(Utils.cutString(getImage().getTitle(), 40, "..."));
        }

    }

    /**
     * set entry limitations
     */
    private void setEntryLimitations(SyndEntry se) {
        if (isRSS_09_or_091N_or_091U()) {
            se.setTitle(Utils.cutString(se.getTitle(), 100, "..."));
            se.getDescription().setValue(Utils.cutString(se.getDescription().getValue(), 500, "..."));
        }

    }

    /**
     * Removes xml header 
     * @param xml
     * @return 
     */
    private String replaceXmlHeader(String xml) {
        return xml.replaceFirst("<\\?xml version=\"1\\.0\" encoding=\"UTF-8\"\\?>", "");
    }

    /**
     * <code>True</code> for RSS 0.9, 0.91*  
     * @return
     */
    private boolean isRSS_09_or_091N_or_091U() {
        return "rss_0.9".equals(getFeedType()) || getFeedType().contains("rss_0.91");
    }

}