org.shredzone.cilla.view.FeedView.java Source code

Java tutorial

Introduction

Here is the source code for org.shredzone.cilla.view.FeedView.java

Source

/*
 * cilla - Blog Management System
 *
 * Copyright (C) 2012 Richard "Shred" Krber
 *   http://cilla.shredzone.org
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.shredzone.cilla.view;

import static java.util.stream.Collectors.toList;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.shredzone.cilla.core.model.Category;
import org.shredzone.cilla.core.model.Page;
import org.shredzone.cilla.core.model.Tag;
import org.shredzone.cilla.core.model.User;
import org.shredzone.cilla.core.repository.PageDao;
import org.shredzone.cilla.service.link.LinkService;
import org.shredzone.cilla.service.search.FilterModel;
import org.shredzone.cilla.service.search.PaginatorModel;
import org.shredzone.cilla.service.search.SearchResult;
import org.shredzone.cilla.service.search.SearchService;
import org.shredzone.cilla.web.FeedType;
import org.shredzone.cilla.web.format.TextFormatter;
import org.shredzone.cilla.ws.exception.CillaServiceException;
import org.shredzone.commons.view.ViewContext;
import org.shredzone.commons.view.annotation.PathPart;
import org.shredzone.commons.view.annotation.View;
import org.shredzone.commons.view.annotation.ViewHandler;
import org.shredzone.commons.view.exception.ErrorResponseException;
import org.shredzone.commons.view.exception.PageNotFoundException;
import org.shredzone.commons.view.exception.ViewException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

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.SyndFeed;
import com.sun.syndication.feed.synd.SyndFeedImpl;
import com.sun.syndication.feed.synd.SyndLink;
import com.sun.syndication.feed.synd.SyndLinkImpl;
import com.sun.syndication.feed.synd.SyndPerson;
import com.sun.syndication.feed.synd.SyndPersonImpl;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedOutput;

/**
 * Views for generating atom and rss feeds for categories, tags and users.
 *
 * @author Richard "Shred" Krber
 */
@ViewHandler
@Component
public class FeedView extends AbstractView {

    private @Value("${feed.maxEntries}") int maxEntries;
    private @Value("${site.name}") String title;
    private @Value("${site.description}") String description;

    private @Resource SearchService searchService;
    private @Resource PageDao pageDao;
    private @Resource TextFormatter textFormatter;
    private @Resource LinkService linkService;

    /**
     * A feed for all pages.
     */
    @View(pattern = "/feed/list.${#feed}", signature = { "date", "#feed" })
    @View(pattern = "/feed/list.${#feed}", signature = { "#feed" })
    public void feedListView(@PathPart("#feed") String suffix, HttpServletRequest req, HttpServletResponse resp,
            ViewContext context) throws ViewException, CillaServiceException {
        String selfUrl = linkService.linkTo().param("feed", suffix).absolute().toString();
        FilterModel filter = new FilterModel();
        fetchPages(filter, context, req, resp, selfUrl, "list", convertFeedType(suffix));
    }

    /**
     * A feed for a single category.
     */
    @View(pattern = "/feed/category/${category.id}/${#simplify(category.name)}.${#feed}", signature = { "category",
            "date", "#feed" })
    @View(pattern = "/feed/category/${category.id}/${#simplify(category.name)}.${#feed}", signature = { "category",
            "#feed" })
    public void feedCategoryView(@PathPart("category.id") Category category, @PathPart("#feed") String suffix,
            HttpServletRequest req, HttpServletResponse resp, ViewContext context)
            throws ViewException, CillaServiceException {
        String selfUrl = linkService.linkTo().category(category).param("feed", suffix).absolute().toString();
        FilterModel filter = new FilterModel();
        filter.setCategory(category);
        fetchPages(filter, context, req, resp, selfUrl, "category-" + category.getId(), convertFeedType(suffix));
    }

    /**
     * A feed for a single tag.
     */
    @View(pattern = "/feed/tag/${#encode(tag.name)}.${#feed}", signature = { "tag", "date", "#feed" })
    @View(pattern = "/feed/tag/${#encode(tag.name)}.${#feed}", signature = { "tag", "#feed" })
    public void feedTagView(@PathPart("#encode(tag.name)") Tag tag, @PathPart("#feed") String suffix,
            HttpServletRequest req, HttpServletResponse resp, ViewContext context)
            throws ViewException, CillaServiceException {
        String selfUrl = linkService.linkTo().tag(tag).param("feed", suffix).absolute().toString();
        FilterModel filter = new FilterModel();
        filter.setTag(tag);
        fetchPages(filter, context, req, resp, selfUrl, "tag-" + tag.getName(), convertFeedType(suffix));
    }

    /**
     * A feed for a single author.
     */
    @View(pattern = "/feed/author/${author.id}/${#simplify(author.name)}.${#feed}", signature = { "author", "date",
            "#feed" })
    @View(pattern = "/feed/author/${author.id}/${#simplify(author.name)}.${#feed}", signature = { "author",
            "#feed" })
    public void feedAuthorView(@PathPart("author.id") User user, @PathPart("#feed") String suffix,
            HttpServletRequest req, HttpServletResponse resp, ViewContext context)
            throws ViewException, CillaServiceException {
        String selfUrl = linkService.linkTo().author(user).param("feed", suffix).absolute().toString();
        FilterModel filter = new FilterModel();
        filter.setCreator(user);
        fetchPages(filter, context, req, resp, selfUrl, "author-" + user.getId(), convertFeedType(suffix));
    }

    /**
     * Converts the feed's suffix to a ROME type.
     *
     * @param suffix
     *            the feed's suffix
     * @return ROME type
     * @throws PageNotFoundException
     *             if the feed suffix is unknown
     */
    private String convertFeedType(String suffix) throws PageNotFoundException {
        return Arrays.stream(FeedType.values()).filter(type -> type.getSuffix().equals(suffix))
                .map(FeedType::getType).findFirst()
                .orElseThrow(() -> new PageNotFoundException("Unknown feed type " + suffix));
    }

    /**
     * Fetches the pages by query and returns a feed.
     *
     * @param filter
     *            {@link FilterModel} with the query parameters
     * @param context
     *            {@link ViewContext} with the view context
     * @param req
     *            the {@link HttpServletRequest}
     * @param resp
     *            the {@link HttpServletResponse}
     * @param selfUrl
     *            freshly generated link to this feed
     * @param feedId
     *            a unique id for this feed
     * @param type
     *            feed type, any type supported by ROME.
     */
    @SuppressWarnings("unchecked")
    private void fetchPages(FilterModel filter, ViewContext context, HttpServletRequest req,
            HttpServletResponse resp, String selfUrl, String feedId, String type)
            throws ViewException, CillaServiceException {
        Date lastModified = pageDao.fetchMinMaxModification()[1];

        if (isNotModifiedSince(req, lastModified)) {
            throw new ErrorResponseException(HttpServletResponse.SC_NOT_MODIFIED);
        }

        SearchResult result = searchService.search(filter);
        result.setPaginator(new PaginatorModel(maxEntries));

        String contentType = "application/xml";
        if (type.startsWith("atom_")) {
            contentType = "application/atom+xml";
        } else if (type.startsWith("rss_")) {
            contentType = "application/rss+xml";
        }

        String uriPrefix = context.getRequestServerUrl() + '/';

        SyndFeed feed = new SyndFeedImpl();
        feed.setFeedType(type);
        feed.setTitle(title);
        feed.setDescription(description);
        feed.setLink(linkService.linkTo().absolute().toString());
        feed.setUri(uriPrefix + feedId);
        feed.setPublishedDate(lastModified);

        SyndLink selfLink = new SyndLinkImpl();
        selfLink.setRel("self");
        selfLink.setType(contentType);
        selfLink.setHref(selfUrl);
        feed.getLinks().add(selfLink);
        feed.getEntries().addAll(result.getPages().stream().filter(page -> !page.isRestricted()) // do not show restricted pages
                .map(page -> createEntry(page, uriPrefix)).collect(Collectors.toList()));

        resp.setContentType(contentType);
        resp.setDateHeader("Last-Modified", lastModified.getTime());
        resp.setCharacterEncoding("utf-8");

        try {
            SyndFeedOutput output = new SyndFeedOutput();
            output.output(feed, resp.getWriter());
        } catch (FeedException | IOException ex) {
            throw new ViewException("Could not create feed", ex);
        }
    }

    /**
     * Creates a {@link SyndEntry} from the given {@link Page}.
     *
     * @param page
     *            {@link Page} to create a {@link SyndEntry} for
     * @param uriPrefix
     *            URI prefix for this website
     * @return {@link SyndEntry} that was created
     */
    @SuppressWarnings("unchecked")
    private SyndEntry createEntry(Page page, String uriPrefix) {
        String pageUrl = linkService.linkTo().page(page).absolute().toString();

        String body = textFormatter.format(page.getTeaser(), () -> linkService.linkTo().page(page)).toString();

        SyndEntry entry = new SyndEntryImpl();
        entry.setTitle(page.getTitle());
        entry.setLink(pageUrl);
        entry.setUri(buildTaxonomyUri(uriPrefix, "page", page.getId()));
        entry.setPublishedDate(page.getPublication());
        entry.setUpdatedDate(page.getModification());

        // Add a donation link if set
        if (page.getDonateUrl() != null) {
            SyndLink link = new SyndLinkImpl();
            link.setRel("payment");
            link.setHref(page.getDonateUrl());
            entry.getLinks().add(link);
        }

        SyndContent description = new SyndContentImpl();
        description.setType("text/html");
        description.setValue(body);
        entry.setDescription(description);

        entry.getCategories().addAll(page.getCategories().stream().map(cat -> {
            SyndCategory category = new SyndCategoryImpl();
            category.setName(cat.getName());
            category.setTaxonomyUri(buildTaxonomyUri(uriPrefix, "category", cat.getId()));
            return category;
        }).collect(toList()));

        entry.getCategories().addAll(page.getTags().stream().map(tag -> {
            SyndCategory category = new SyndCategoryImpl();
            category.setName(tag.getName());
            category.setTaxonomyUri(buildTaxonomyUri(uriPrefix, "tag", tag.getName()));
            return category;
        }).collect(toList()));

        List<SyndPerson> authorList = new ArrayList<>();
        User creator = page.getCreator();
        SyndPerson author = new SyndPersonImpl();
        author.setName(creator.getName());
        author.setUri(buildTaxonomyUri(uriPrefix, "author", creator.getId()));
        authorList.add(author);
        entry.setAuthors(authorList);

        return entry;
    }

    /**
     * Builds a taxonomy URL.
     *
     * @param prefix
     *            URI prefix for this web site
     * @param type
     *            feed type (e.g. "author")
     * @param id
     *            unique id of the feed's entity
     * @return generated URI
     */
    private String buildTaxonomyUri(String prefix, String type, long id) {
        return buildTaxonomyUri(prefix, type, String.valueOf(id));
    }

    /**
     * Builds a taxonomy URL.
     *
     * @param prefix
     *            URI prefix for this web site
     * @param type
     *            feed type (e.g. "author")
     * @param id
     *            unique id String of the feed's entity
     * @return generated URI
     */
    private String buildTaxonomyUri(String prefix, String type, String id) {
        try {
            return prefix + type + '-' + URLEncoder.encode(id, "utf-8");
        } catch (UnsupportedEncodingException ex) {
            throw new InternalError("utf-8 missing");
        }
    }

}