org.opentravel.pubs.controllers.PublicationController.java Source code

Java tutorial

Introduction

Here is the source code for org.opentravel.pubs.controllers.PublicationController.java

Source

/**
 * Copyright (C) 2015 OpenTravel Alliance (info@opentravel.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.opentravel.pubs.controllers;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.opentravel.pubs.builders.ArtifactCommentBuilder;
import org.opentravel.pubs.builders.SchemaCommentBuilder;
import org.opentravel.pubs.dao.CodeListDAO;
import org.opentravel.pubs.dao.CommentDAO;
import org.opentravel.pubs.dao.DAOException;
import org.opentravel.pubs.dao.DAOFactoryManager;
import org.opentravel.pubs.dao.DownloadDAO;
import org.opentravel.pubs.dao.PublicationDAO;
import org.opentravel.pubs.dao.RegistrantDAO;
import org.opentravel.pubs.forms.ArtifactCommentForm;
import org.opentravel.pubs.forms.RegistrantForm;
import org.opentravel.pubs.forms.SchemaCommentForm;
import org.opentravel.pubs.forms.ViewSpecificationForm;
import org.opentravel.pubs.model.ArtifactComment;
import org.opentravel.pubs.model.CodeList;
import org.opentravel.pubs.model.CommentType;
import org.opentravel.pubs.model.Publication;
import org.opentravel.pubs.model.PublicationGroup;
import org.opentravel.pubs.model.PublicationItem;
import org.opentravel.pubs.model.PublicationItemType;
import org.opentravel.pubs.model.PublicationState;
import org.opentravel.pubs.model.PublicationType;
import org.opentravel.pubs.model.Registrant;
import org.opentravel.pubs.model.SchemaComment;
import org.opentravel.pubs.util.StringUtils;
import org.opentravel.pubs.util.ValueFormatter;
import org.opentravel.pubs.validation.ModelValidator;
import org.opentravel.pubs.validation.ValidationException;
import org.opentravel.pubs.validation.ValidationResults;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

/**
 * Controller that handles interactions with the publication pages of the OpenTravel
 * specification site.
 * 
 * @author S. Livezey
 */
@Controller
@RequestMapping("/specifications")
public class PublicationController extends BaseController {

    private static final Logger log = LoggerFactory.getLogger(PublicationController.class);

    @RequestMapping({ "/Specifications.html", "/Specifications.htm", "/Specifications10.html",
            "/Specifications10.htm" })
    public String specifications10PublicPage(Model model, HttpSession session,
            @RequestParam(value = "spec", required = false) String spec,
            @RequestParam(value = "newSession", required = false) boolean newSession,
            @ModelAttribute("specificationForm") ViewSpecificationForm specificationForm) {

        return doSpecificationPage(model, session, spec, newSession, specificationForm, PublicationType.OTA_1_0,
                false, "specification10Main", "Specifications10.html");
    }

    @RequestMapping({ "/Specifications20.html", "/Specifications20.htm" })
    public String specifications20PublicPage(Model model, HttpSession session,
            @RequestParam(value = "spec", required = false) String spec,
            @RequestParam(value = "newSession", required = false) boolean newSession,
            @ModelAttribute("specificationForm") ViewSpecificationForm specificationForm) {

        return doSpecificationPage(model, session, spec, newSession, specificationForm, PublicationType.OTA_2_0,
                false, "specification20Main", "Specifications20.html");
    }

    @RequestMapping({ "/members/Specifications.html", "/members/Specifications.htm",
            "/members/Specifications10.html", "/members/Specifications10.htm" })
    public String specifications10MemberPage(Model model, HttpSession session,
            @RequestParam(value = "spec", required = false) String spec,
            @RequestParam(value = "newSession", required = false) boolean newSession,
            @ModelAttribute("specificationForm") ViewSpecificationForm specificationForm) {

        return doSpecificationPage(model, session, spec, newSession, specificationForm, PublicationType.OTA_1_0,
                true, "specification10MainMembers", "members/Specifications10.html");
    }

    @RequestMapping({ "/members/Specifications20.html", "/members/Specifications20.htm" })
    public String specifications20MemberPage(Model model, HttpSession session,
            @RequestParam(value = "spec", required = false) String spec,
            @RequestParam(value = "newSession", required = false) boolean newSession,
            @ModelAttribute("specificationForm") ViewSpecificationForm specificationForm) {

        return doSpecificationPage(model, session, spec, newSession, specificationForm, PublicationType.OTA_2_0,
                true, "specification20MainMembers", "members/Specifications20.html");
    }

    @RequestMapping({ "/ReleaseNotes.html", "/ReleaseNotes.htm" })
    public String releaseNotesPublicPage(Model model, HttpSession session,
            @RequestParam(value = "spec", required = false) String spec,
            @RequestParam(value = "specType", required = false) String specType) {

        return doReleaseNotesPage(model, session, spec, specType, "specificationReleaseNotes", false);
    }

    @RequestMapping({ "/members/ReleaseNotes.html", "/members/ReleaseNotes.htm" })
    public String releaseNotesMemberPage(Model model, HttpSession session,
            @RequestParam(value = "spec", required = false) String spec,
            @RequestParam(value = "specType", required = false) String specType) {

        return doReleaseNotesPage(model, session, spec, specType, "specificationReleaseNotesMembers", true);
    }

    @RequestMapping({ "/CodeLists.html", "/CodeLists.htm" })
    public String codeListPublicPage(Model model, HttpSession session,
            @RequestParam(value = "releaseDate", required = false) String releaseDateStr,
            @RequestParam(value = "newSession", required = false) boolean newSession,
            @ModelAttribute("specificationForm") ViewSpecificationForm specificationForm) {
        String targetPage = "codeListMain";

        try {
            RegistrantForm registrantForm = specificationForm.getRegistrantForm();
            CodeListDAO cDao = DAOFactoryManager.getFactory().newCodeListDAO();
            CodeList codeList = null;

            if ((releaseDateStr != null) && (releaseDateStr.trim().length() > 0)) {
                try {
                    codeList = cDao.getCodeList(CodeList.labelFormat.parse(releaseDateStr.trim()));

                } catch (ParseException e) {
                    setErrorMessage("Invalid release date requested: \"" + releaseDateStr + "\"", model);
                }
            }
            if (codeList == null) {
                codeList = cDao.getLatestCodeList();
            }

            model.addAttribute("codeList", codeList);
            model.addAttribute("registrationPage", "CodeLists.html");

            if (newSession)
                session.removeAttribute("registrantId");
            handleRegistrantInfo(registrantForm, model, session);

            // If we processed the form successfully, clear our model so that its
            // attributes will not show up as URL parameters on redirect
            if (registrantForm.isProcessForm() && (model.asMap().get("registrant") != null)) {
                targetPage = "redirect:/specifications/CodeLists.html";
                model.asMap().clear();
            }
            specificationForm.setProcessForm(true);

        } catch (Throwable t) {
            log.error("Error during publication controller processing.", t);
            setErrorMessage(DEFAULT_ERROR_MESSAGE, model);
        }
        return applyCommonValues(model, targetPage);
    }

    @RequestMapping({ "/Comment10Spec.html", "/Comment10Spec.htm" })
    public String comment10SpecPublicPage(Model model, HttpSession session, RedirectAttributes redirectAttrs,
            @RequestParam(value = "newSession", required = false) boolean newSession,
            @ModelAttribute("commentForm") SchemaCommentForm commentForm) {

        return doCommentSpecPage(model, session, redirectAttrs, newSession, commentForm, PublicationType.OTA_1_0,
                false, "specificationComment10", "/specifications/Comment10Spec.html");
    }

    @RequestMapping({ "/Comment20Spec.html", "/Comment20Spec.htm" })
    public String comment20SpecPublicPage(Model model, HttpSession session, RedirectAttributes redirectAttrs,
            @RequestParam(value = "newSession", required = false) boolean newSession,
            @ModelAttribute("commentForm") SchemaCommentForm commentForm) {

        return doCommentSpecPage(model, session, redirectAttrs, newSession, commentForm, PublicationType.OTA_2_0,
                false, "specificationComment20", "/specifications/Comment20Spec.html");
    }

    @RequestMapping({ "/members/Comment10Spec.html", "/members/Comment10Spec.htm" })
    public String comment10SpecMemberPage(Model model, HttpSession session, RedirectAttributes redirectAttrs,
            @RequestParam(value = "newSession", required = false) boolean newSession,
            @ModelAttribute("commentForm") SchemaCommentForm commentForm) {

        return doCommentSpecPage(model, session, redirectAttrs, newSession, commentForm, PublicationType.OTA_1_0,
                true, "specificationComment10Members", "/specifications/members/Comment10Spec.html");
    }

    @RequestMapping({ "/members/Comment20Spec.html", "/members/Comment20Spec.htm" })
    public String comment20SpecMemberPage(Model model, HttpSession session, RedirectAttributes redirectAttrs,
            @RequestParam(value = "newSession", required = false) boolean newSession,
            @ModelAttribute("commentForm") SchemaCommentForm commentForm) {

        return doCommentSpecPage(model, session, redirectAttrs, newSession, commentForm, PublicationType.OTA_2_0,
                true, "specificationComment20Members", "/specifications/members/Comment20Spec.html");
    }

    @RequestMapping({ "/Comment10Artifact.html", "/Comment10Artifact.html" })
    public String comment10ArtifactPublicPage(Model model, HttpSession session, RedirectAttributes redirectAttrs,
            @RequestParam(value = "newSession", required = false) boolean newSession,
            @ModelAttribute("commentForm") ArtifactCommentForm commentForm) {

        return doCommentArtifactPage(model, session, redirectAttrs, newSession, commentForm,
                PublicationType.OTA_1_0, false, "artifactComment10", "/specifications/Comment10Artifact.html");
    }

    @RequestMapping({ "/Comment20Artifact.html", "/Comment20Artifact.htm" })
    public String comment20ArtifactPublicPage(Model model, HttpSession session, RedirectAttributes redirectAttrs,
            @RequestParam(value = "newSession", required = false) boolean newSession,
            @ModelAttribute("commentForm") ArtifactCommentForm commentForm) {

        return doCommentArtifactPage(model, session, redirectAttrs, newSession, commentForm,
                PublicationType.OTA_2_0, false, "artifactComment20", "/specifications/Comment20Artifact.html");
    }

    @RequestMapping({ "/members/Comment10Artifact.html", "/members/Comment10Artifact.html" })
    public String comment10ArtifactMemberPage(Model model, HttpSession session, RedirectAttributes redirectAttrs,
            @RequestParam(value = "newSession", required = false) boolean newSession,
            @ModelAttribute("commentForm") ArtifactCommentForm commentForm) {

        return doCommentArtifactPage(model, session, redirectAttrs, newSession, commentForm,
                PublicationType.OTA_1_0, true, "artifactComment10Members",
                "/specifications/members/Comment10Artifact.html");
    }

    @RequestMapping({ "/members/Comment20Artifact.html", "/members/Comment20Artifact.htm" })
    public String comment20ArtifactMemberPage(Model model, HttpSession session, RedirectAttributes redirectAttrs,
            @RequestParam(value = "newSession", required = false) boolean newSession,
            @ModelAttribute("commentForm") ArtifactCommentForm commentForm) {

        return doCommentArtifactPage(model, session, redirectAttrs, newSession, commentForm,
                PublicationType.OTA_2_0, true, "artifactComment20Members",
                "/specifications/members/Comment20Artifact.html");
    }

    @RequestMapping({ "/CommentThankYou.html", "/CommentThankYou.htm" })
    public String commentThankYouPublicPage(Model model,
            @RequestParam(value = "submitCommentsUrl", required = false) String submitCommentsUrl) {
        model.addAttribute("submitCommentsUrl", submitCommentsUrl);
        return applyCommonValues(model, "commentThankYou");
    }

    @RequestMapping({ "/members/CommentThankYou.html", "/members/CommentThankYou.htm" })
    public String commentThankYouMemberPage(Model model,
            @RequestParam(value = "submitCommentsUrl", required = false) String submitCommentsUrl) {
        model.addAttribute("submitCommentsUrl", submitCommentsUrl);
        return applyCommonValues(model, "commentThankYouMembers");
    }

    @RequestMapping({ "/ModelViewer.html", "/ModelViewer.htm" })
    public String modelViewerPage(Model model) {
        try {
            PublicationDAO dao = DAOFactoryManager.getFactory().newPublicationDAO();
            Publication publication = dao.getLatestPublication(PublicationType.OTA_1_0);

            model.addAttribute("publication", publication);

        } catch (Throwable t) {
            log.error("Error during publication controller processing.", t);
            setErrorMessage(DEFAULT_ERROR_MESSAGE, model);
        }
        return applyCommonValues(model, "modelViewer");
    }

    @RequestMapping({ "/OnlinePublications.html", "/OnlinePublications.htm" })
    public String onlineArtifactsPage(Model model) {
        try {
            PublicationDAO pDao = DAOFactoryManager.getFactory().newPublicationDAO();
            List<Publication> publications10 = pDao.getAllPublications(PublicationType.OTA_1_0);
            List<Publication> publications20 = pDao.getAllPublications(PublicationType.OTA_2_0);

            // This page is only available in the public area, so purge all member-review
            // specifications from the list
            purgeMemberReviewSpecifications(publications10);
            purgeMemberReviewSpecifications(publications20);

            model.addAttribute("publications10", publications10);
            model.addAttribute("publications20", publications20);

        } catch (Throwable t) {
            log.error("Error during publication controller processing.", t);
            setErrorMessage(DEFAULT_ERROR_MESSAGE, model);
        }
        return applyCommonValues(model, "onlinePublications");
    }

    @RequestMapping({ "/OnlinePublicationDetails.html", "/OnlinePublicationDetails.htm" })
    public String onlineArtifactDetailsPage(Model model, HttpSession session,
            @RequestParam(value = "spec", required = false) String spec,
            @RequestParam(value = "specType", required = false) String specType,
            @RequestParam(value = "group", required = false) Long selectedGroupId) {
        try {
            PublicationDAO pDao = DAOFactoryManager.getFactory().newPublicationDAO();
            PublicationType pubType = resolvePublicationType(specType);
            Publication publication = pDao.getPublication(spec, pubType);
            List<PublicationGroup> groupList = publication.getPublicationGroups();
            List<PublicationItem> itemList;
            PublicationGroup selectedGroup = ((groupList == null) || groupList.isEmpty()) ? null : groupList.get(0);

            if (selectedGroupId != null) {
                for (PublicationGroup group : groupList) {
                    if (group.getId() == selectedGroupId) {
                        selectedGroup = group;
                        break;
                    }
                }
            }
            itemList = (selectedGroup == null) ? null : selectedGroup.getPublicationItems();

            model.addAttribute("publication", publication);
            model.addAttribute("selectedGroup", selectedGroup.getId());
            model.addAttribute("groupList", groupList);
            model.addAttribute("itemList", itemList);
            model.addAttribute("formatter", ValueFormatter.getInstance());

        } catch (Throwable t) {
            log.error("Error during publication controller processing.", t);
            setErrorMessage(DEFAULT_ERROR_MESSAGE, model);
        }
        return applyCommonValues(model, "onlinePublicationDetails");
    }

    @RequestMapping({ "/PastSpecs.html", "/PastSpecs.htm" })
    public String pastSpecsPage(Model model) {
        try {
            PublicationDAO pDao = DAOFactoryManager.getFactory().newPublicationDAO();
            CodeListDAO cDao = DAOFactoryManager.getFactory().newCodeListDAO();
            List<Publication> publications10 = pDao.getAllPublications(PublicationType.OTA_1_0);
            List<Publication> publications20 = pDao.getAllPublications(PublicationType.OTA_2_0);
            List<CodeList> codeLists = cDao.getAllCodeLists();

            // This page is only available in the public area, so purge all member-review
            // specifications from the list
            purgeMemberReviewSpecifications(publications10);
            purgeMemberReviewSpecifications(publications20);

            // Remove the first element of each list since it is the current specification
            if (!publications10.isEmpty()) {
                publications10.remove(0);
            }
            if (!publications20.isEmpty()) {
                publications20.remove(0);
            }
            if (!codeLists.isEmpty()) {
                codeLists.remove(0);
            }
            model.addAttribute("publications10", publications10);
            model.addAttribute("publications20", publications20);
            model.addAttribute("codeLists", codeLists);

        } catch (Throwable t) {
            log.error("Error during publication controller processing.", t);
            setErrorMessage(DEFAULT_ERROR_MESSAGE, model);
        }
        return applyCommonValues(model, "pastSpecifications");
    }

    @RequestMapping({ "/FAQ.html", "/FAQ.htm" })
    public String faqPage(Model model) {
        return applyCommonValues(model, "specificationFAQ");
    }

    @RequestMapping({ "/DownloadRegister.html", "/DownloadRegister.htm" })
    public String downloadPublicationRegisterPage(Model model, HttpSession session,
            @RequestParam(value = "pubName", required = true) String pubName,
            @RequestParam(value = "pubType", required = true) String type,
            @RequestParam(value = "filename", required = true) String filename,
            @ModelAttribute("specificationForm") ViewSpecificationForm specificationForm) {
        String targetPage = "downloadRegister";
        try {
            RegistrantForm registrantForm = specificationForm.getRegistrantForm();
            PublicationDAO pDao = DAOFactoryManager.getFactory().newPublicationDAO();
            PublicationType pubType = resolvePublicationType(type);
            Publication publication = pDao.getPublication(pubName, pubType);

            handleRegistrantInfo(registrantForm, model, session);

            // If we processed the form successfully, clear our model so that its
            // attributes will not show up as URL parameters on redirect
            if (registrantForm.isProcessForm() && (model.asMap().get("registrant") != null)) {
                targetPage = "redirect:/content/specifications/downloads/" + pubName + "/" + type + "/" + filename;
                model.asMap().clear();

            } else {
                model.addAttribute("publication", publication);
                model.addAttribute("pubName", pubName);
                model.addAttribute("pubType", pubType);
                model.addAttribute("filename", filename);
            }
            specificationForm.setProcessForm(true);

        } catch (Throwable t) {
            log.error("Error during publication controller processing.", t);
            setErrorMessage(DEFAULT_ERROR_MESSAGE, model);
        }
        return applyCommonValues(model, targetPage);
    }

    @RequestMapping({ "/downloads/{pubName}/{pubType}/{filename:.+}" })
    public String downloadPublication(Model model, HttpSession session, HttpServletRequest request,
            HttpServletResponse response, RedirectAttributes redirectAttrs, @PathVariable("pubName") String pubName,
            @PathVariable("pubType") String type, @PathVariable("filename") String filename) {
        String targetPath = null;
        try {
            Registrant registrant = getCurrentRegistrant(session);

            if (registrant == null) {
                model.asMap().clear();
                redirectAttrs.addAttribute("pubName", pubName);
                redirectAttrs.addAttribute("pubType", type);
                redirectAttrs.addAttribute("filename", filename);
                targetPath = "redirect:/specifications/DownloadRegister.html";

            } else {
                targetPath = doPublicationDownload(model, session, registrant, request, response, redirectAttrs,
                        pubName, type, filename);
            }

        } catch (Throwable t) {
            log.error("Error during publication controller processing.", t);
            setErrorMessage(DEFAULT_ERROR_MESSAGE, model);
        }
        return applyCommonValues(model, targetPath);
    }

    @RequestMapping({ "/downloads/noregister/{pubName}/{pubType}/{filename:.+}" })
    public String downloadPublicationNoRegistration(Model model, HttpSession session, HttpServletRequest request,
            HttpServletResponse response, RedirectAttributes redirectAttrs, @PathVariable("pubName") String pubName,
            @PathVariable("pubType") String type, @PathVariable("filename") String filename) {
        String targetPath = null;
        try {
            Registrant registrant = getCurrentRegistrant(session);

            // There is no requirement to register for this download link.  If the user HAS already
            // registered, however, we will associate this download with their registration identity.
            targetPath = doPublicationDownload(model, session, registrant, request, response, redirectAttrs,
                    pubName, type, filename);

        } catch (Throwable t) {
            log.error("Error during publication controller processing.", t);
            setErrorMessage(DEFAULT_ERROR_MESSAGE, model);
        }
        return applyCommonValues(model, targetPath);
    }

    @RequestMapping({ "/cl/DownloadRegister.html", "/cl/DownloadRegister.htm" })
    public String downloadCodeListRegisterPage(Model model, HttpSession session,
            @RequestParam(value = "releaseDate", required = true) String releaseDateStr,
            @RequestParam(value = "filename", required = true) String filename,
            @ModelAttribute("specificationForm") ViewSpecificationForm specificationForm) {
        String targetPage = "downloadCodeListRegister";
        try {
            RegistrantForm registrantForm = specificationForm.getRegistrantForm();
            CodeListDAO cDao = DAOFactoryManager.getFactory().newCodeListDAO();
            try {
                CodeList codeList = cDao.getCodeList(CodeList.labelFormat.parse(releaseDateStr));

                handleRegistrantInfo(registrantForm, model, session);

                // If we processed the form successfully, clear our model so that its
                // attributes will not show up as URL parameters on redirect
                if (registrantForm.isProcessForm() && (model.asMap().get("registrant") != null)) {
                    targetPage = "redirect:/content/specifications/downloads/cl/" + releaseDateStr + "/" + filename;
                    model.asMap().clear();

                } else {
                    model.addAttribute("codeList", codeList);
                    model.addAttribute("releaseDate", releaseDateStr);
                    model.addAttribute("filename", filename);
                }
                specificationForm.setProcessForm(true);

            } catch (ParseException e) {
                setErrorMessage("Invalid release date requested: \"" + releaseDateStr + "\"", model);
            }

        } catch (Throwable t) {
            log.error("Error during publication controller processing.", t);
            setErrorMessage(DEFAULT_ERROR_MESSAGE, model);
        }
        return applyCommonValues(model, targetPage);
    }

    @RequestMapping({ "/downloads/cl/{releaseDate}/{filename:.+}" })
    public String downloadCodeList(Model model, HttpSession session, HttpServletRequest request,
            HttpServletResponse response, RedirectAttributes redirectAttrs,
            @PathVariable("releaseDate") String releaseDateStr, @PathVariable("filename") String filename) {
        String targetPath = null;
        try {
            Registrant registrant = getCurrentRegistrant(session);

            if (registrant == null) {
                model.asMap().clear();
                redirectAttrs.addAttribute("releaseDate", releaseDateStr);
                redirectAttrs.addAttribute("filename", filename);
                targetPath = "redirect:/specifications/cl/DownloadRegister.html";

            } else {
                targetPath = doCodeListDownload(model, session, registrant, request, response, redirectAttrs,
                        releaseDateStr, filename);
            }

        } catch (Throwable t) {
            log.error("Error during publication controller processing.", t);
            setErrorMessage(DEFAULT_ERROR_MESSAGE, model);
        }
        return applyCommonValues(model, targetPath);
    }

    @RequestMapping({ "/DownloadNotFound.html", "/DownloadNotFound.htm" })
    public String downloadNotFoundPage(Model model, @RequestParam(value = "filename") String filename) {
        model.addAttribute("filename", filename);
        return applyCommonValues(model, "downloadNotFound");
    }

    /**
     * Performs a download of the publication or publication item content specified by the URL
     * parameters.
     * 
     * @param model  the UI model for the current request
     * @param session  the HTTP session
     * @param registrant  the web site registrant who is performing the download
     * @param request  the HTTP request for the content download
     * @param response  the HTTP response to which output should be directed
     * @param redirectAttrs  request attributes that must be available in case of a page redirect
     * @param pubName  the name of the publication from which content is being downloaded
     * @param type  the type of the publication
     * @param filename  the name of the content item that is being downloaded
     * @return String
     * @throws DAOException  thrown if an error occurs while accessing the content from persistent storage
     * @throws IOException  thrown if the content cannot be streamed to the HTTP response
     */
    protected String doPublicationDownload(Model model, HttpSession session, Registrant registrant,
            HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttrs,
            String pubName, String type, String filename) throws DAOException, IOException {
        InputStream contentStream = null;
        String targetPath = null;

        if ((pubName != null) && (type != null) && (filename != null)) {
            PublicationDAO pDao = DAOFactoryManager.getFactory().newPublicationDAO();
            DownloadDAO dDao = DAOFactoryManager.getFactory().newDownloadDAO();
            PublicationType pubType = resolvePublicationType(type);
            Publication publication = pDao.getPublication(pubName, pubType);

            if (publication != null) {
                if (filename.equals(publication.getArchiveFilename())) {
                    contentStream = dDao.getArchiveContent(publication, registrant);

                } else {
                    PublicationItem item = pDao.findPublicationItem(publication, filename);

                    if (item != null) {
                        contentStream = dDao.getContent(item, registrant);
                    }
                }
            }
        }

        if (contentStream != null) {
            try (OutputStream responseOut = response.getOutputStream()) {
                byte[] buffer = new byte[BUFFER_SIZE];
                int bytesRead;

                while ((bytesRead = contentStream.read(buffer, 0, buffer.length)) >= 0) {
                    responseOut.write(buffer, 0, bytesRead);
                }

            } finally {
                try {
                    contentStream.close();
                } catch (Throwable t) {
                }
            }

        } else {
            model.asMap().clear();
            redirectAttrs.addAttribute("filename", request.getRequestURL());
            targetPath = "redirect:/specifications/DownloadNotFound.html";
        }
        return targetPath;
    }

    /**
     * Performs a download of the publication or publication item content specified by the URL
     * parameters.
     * 
     * @param model  the UI model for the current request
     * @param session  the HTTP session
     * @param registrant  the web site registrant who is performing the download
     * @param request  the HTTP request for the content download
     * @param response  the HTTP response to which output should be directed
     * @param redirectAttrs  request attributes that must be available in case of a page redirect
     * @param releaseDateStr  the release date of the code list archive being downloaded
     * @param filename  the name of the content item that is being downloaded
     * @return String
     * @throws DAOException  thrown if an error occurs while accessing the content from persistent storage
     * @throws IOException  thrown if the content cannot be streamed to the HTTP response
     */
    protected String doCodeListDownload(Model model, HttpSession session, Registrant registrant,
            HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttrs,
            String releaseDateStr, String filename) throws DAOException, IOException {
        InputStream contentStream = null;
        String targetPath = null;

        if ((releaseDateStr != null) && (releaseDateStr.trim().length() > 0)) {
            CodeListDAO cDao = DAOFactoryManager.getFactory().newCodeListDAO();
            DownloadDAO dDao = DAOFactoryManager.getFactory().newDownloadDAO();

            try {
                CodeList codeList = cDao.getCodeList(CodeList.labelFormat.parse(releaseDateStr));

                if (codeList != null) {
                    if (filename.equals(codeList.getArchiveFilename())) {
                        contentStream = dDao.getArchiveContent(codeList, registrant);
                    }
                }

            } catch (ParseException e) {
                log.warn("Unreadable release date specified for code list download: \"" + releaseDateStr + "\"");
            }
        }

        if (contentStream != null) {
            try (OutputStream responseOut = response.getOutputStream()) {
                byte[] buffer = new byte[BUFFER_SIZE];
                int bytesRead;

                while ((bytesRead = contentStream.read(buffer, 0, buffer.length)) >= 0) {
                    responseOut.write(buffer, 0, bytesRead);
                }

            } finally {
                try {
                    contentStream.close();
                } catch (Throwable t) {
                }
            }

        } else {
            model.asMap().clear();
            redirectAttrs.addAttribute("filename", request.getRequestURL());
            targetPath = "redirect:/specifications/DownloadNotFound.html";
        }
        return targetPath;
    }

    /**
     * Hanldes the processing for all of the view-specification page targets.
     * 
     * @param model  the UI model for the current request
     * @param session  the HTTP session
     * @param spec  indicates the name of the specification to view
     * @param newSession  flag indicating that the user has requested to re-register in their session
     * @param specificationForm  the form used to supply content when a new user registers
     * @param pubType  the type of the publication to view (1.0/2.0)
     * @param isMembersOnlyPage  flag indicating whether the page being viewed is a members-only page
     * @param targetPage  the MVC name of the target page
     * @param altPageLocation  the name of the alternate page location in case of registration or redirect
     * @return String
     */
    private String doSpecificationPage(Model model, HttpSession session, String spec, boolean newSession,
            ViewSpecificationForm specificationForm, PublicationType pubType, boolean isMembersOnlyPage,
            String targetPage, String altPageLocation) {
        try {
            RegistrantForm registrantForm = specificationForm.getRegistrantForm();
            PublicationDAO pDao = DAOFactoryManager.getFactory().newPublicationDAO();
            Publication publication = null;

            if (spec != null) {
                publication = pDao.getPublication(spec, pubType);
            }
            if (publication == null) {
                publication = pDao.getLatestPublication(pubType, getAllowedStates(isMembersOnlyPage));
            }

            model.addAttribute("publication", publication);
            model.addAttribute("registrationPage", altPageLocation);
            model.addAttribute("releaseNotesUrl", isMembersOnlyPage ? "/specifications/members/ReleaseNotes.html"
                    : "/specifications/ReleaseNotes.html");
            setupPublicationCheck(model, isMembersOnlyPage, pubType);

            if (newSession)
                session.removeAttribute("registrantId");
            handleRegistrantInfo(registrantForm, model, session);

            // If we processed the form successfully, clear our model so that its
            // attributes will not show up as URL parameters on redirect
            if (registrantForm.isProcessForm() && (model.asMap().get("registrant") != null)) {
                targetPage = "redirect:/specifications/" + altPageLocation;
                model.asMap().clear();
            }
            specificationForm.setProcessForm(true);

        } catch (Throwable t) {
            log.error("Error during publication controller processing.", t);
            setErrorMessage(DEFAULT_ERROR_MESSAGE, model);
        }
        return applyCommonValues(model, targetPage);
    }

    /**
     * Hanldes the processing for all of the release notes page targets.
     * 
     * @param model  the UI model for the current request
     * @param session  the HTTP session
     * @param spec  indicates the name of the specification to view
     * @param specType  the type of the publication to view (1.0/2.0)
     * @param targetPage  the MVC name of the target page
     * @param isMembersOnlyPage  flag indicating whether the page being viewed is a members-only page
     * @return String
     */
    private String doReleaseNotesPage(Model model, HttpSession session, String spec, String specType,
            String targetPage, boolean isMembersOnlyPage) {
        try {
            PublicationDAO pDao = DAOFactoryManager.getFactory().newPublicationDAO();
            PublicationType pubType = resolvePublicationType(specType);
            Publication publication = pDao.getPublication(spec, pubType);
            Registrant registrant = getCurrentRegistrant(session);

            model.addAttribute("publication", publication);

            if (registrant == null) {
                if (pubType == PublicationType.OTA_1_0) {
                    targetPage = "specification10Main";
                } else {
                    targetPage = "specification20Main";
                }
                if (isMembersOnlyPage) {
                    targetPage += "Members";
                }
            }
            model.addAttribute("releaseNotesText", getReleaseNotesText(publication));
            setupPublicationCheck(model, isMembersOnlyPage, pubType);

        } catch (Throwable t) {
            log.error("Error during publication controller processing.", t);
            setErrorMessage(DEFAULT_ERROR_MESSAGE, model);
        }
        return applyCommonValues(model, targetPage);
    }

    /**
     * Hanldes the processing for all of the schema comment page targets.
     * 
     * @param model  the UI model for the current request
     * @param session  the HTTP session
     * @param redirectAttrs  request attributes that must be available in case of a page redirect
     * @param newSession  flag indicating that the user has requested to re-register in their session
     * @param commentForm  the form used to supply the field values for the submitted comment
     * @param pubType  the type of the publication to view (1.0/2.0)
     * @param isMembersOnlyPage  flag indicating whether the page being viewed is a members-only page
     * @param targetPage  the MVC name of the target page
     * @param submitCommentsUrl  the URL location of the comment submission page
     * @return String
     */
    private String doCommentSpecPage(Model model, HttpSession session, RedirectAttributes redirectAttrs,
            boolean newSession, SchemaCommentForm commentForm, PublicationType pubType, boolean isMembersOnlyPage,
            String targetPage, String submitCommentsUrl) {
        boolean commentSuccess = false;
        try {
            if (newSession)
                session.removeAttribute("registrantId");
            Registrant registrant = getCurrentRegistrant(session);
            boolean processRegistrant = commentForm.isProcessForm() && (registrant == null);
            RegistrantForm registrantForm = commentForm.getRegistrantForm();

            registrantForm.setProcessForm(processRegistrant);
            handleRegistrantInfo(registrantForm, model, session);
            registrant = (Registrant) model.asMap().get("registrant");

            // If the registrant was created successfully, commit it and start a new
            // transaction for the comment
            if (processRegistrant && (registrant != null)) {
                DAOFactoryManager.getFactory().commitTransaction();
                DAOFactoryManager.getFactory().beginTransaction();
                registrant = getCurrentRegistrant(session);
            }

            if (commentForm.isProcessForm()) {
                commentSuccess = addSchemaComment(commentForm, registrant, model, session);
            }

            if (commentSuccess) {
                if (isMembersOnlyPage) {
                    targetPage = "redirect:/specifications/members/CommentThankYou.html";
                } else {
                    targetPage = "redirect:/specifications/CommentThankYou.html";
                }
                redirectAttrs.addAttribute("submitCommentsUrl", submitCommentsUrl);
                model.asMap().clear();

            } else {
                PublicationDAO pDao = DAOFactoryManager.getFactory().newPublicationDAO();
                Publication publication = pDao.getLatestPublication(pubType, getAllowedStates(isMembersOnlyPage));
                List<PublicationItem> publicationItems = new ArrayList<>();

                if (publication != null) {
                    publicationItems.addAll(pDao.getPublicationItems(publication, PublicationItemType.WSDL));
                    publicationItems.addAll(pDao.getPublicationItems(publication, PublicationItemType.XML_SCHEMA));
                    publicationItems.addAll(pDao.getPublicationItems(publication, PublicationItemType.JSON_SCHEMA));
                }

                model.addAttribute("publication", publication);
                model.addAttribute("publicationItems", publicationItems);
                model.addAttribute("commentTypes", Arrays.asList(CommentType.values()));
                setupPublicationCheck(model, isMembersOnlyPage, pubType);
            }
            model.addAttribute("submitCommentsUrl", submitCommentsUrl);
            commentForm.setProcessForm(true);

        } catch (Throwable t) {
            log.error("Error during publication controller processing.", t);
            setErrorMessage(DEFAULT_ERROR_MESSAGE, model);
        }
        return applyCommonValues(model, targetPage);
    }

    /**
     * Hanldes the processing for all of the artifact comment page targets.
     * 
     * @param model  the UI model for the current request
     * @param session  the HTTP session
     * @param redirectAttrs  request attributes that must be available in case of a page redirect
     * @param newSession  flag indicating that the user has requested to re-register in their session
     * @param commentForm  the form used to supply the field values for the submitted comment
     * @param pubType  the type of the publication to view (1.0/2.0)
     * @param isMembersOnlyPage  flag indicating whether the page being viewed is a members-only page
     * @param targetPage  the MVC name of the target page
     * @param submitCommentsUrl  the URL location of the comment submission page
     * @return String
     */
    private String doCommentArtifactPage(Model model, HttpSession session, RedirectAttributes redirectAttrs,
            boolean newSession, ArtifactCommentForm commentForm, PublicationType pubType, boolean isMembersOnlyPage,
            String targetPage, String submitCommentsUrl) {
        boolean commentSuccess = false;
        try {
            if (newSession)
                session.removeAttribute("registrantId");
            Registrant registrant = getCurrentRegistrant(session);
            boolean processRegistrant = commentForm.isProcessForm() && (registrant == null);
            RegistrantForm registrantForm = commentForm.getRegistrantForm();

            registrantForm.setProcessForm(processRegistrant);
            handleRegistrantInfo(registrantForm, model, session);
            registrant = (Registrant) model.asMap().get("registrant");

            // If the registrant was created successfully, commit it and start a new
            // transaction for the comment
            if (processRegistrant && (registrant != null)) {
                DAOFactoryManager.getFactory().commitTransaction();
                DAOFactoryManager.getFactory().beginTransaction();
                registrant = getCurrentRegistrant(session);
            }

            if (commentForm.isProcessForm()) {
                commentSuccess = addArtifactComment(commentForm, registrant, model, session);
            }

            if (commentSuccess) {
                if (isMembersOnlyPage) {
                    targetPage = "redirect:/specifications/members/CommentThankYou.html";
                } else {
                    targetPage = "redirect:/specifications/CommentThankYou.html";
                }
                redirectAttrs.addAttribute("submitCommentsUrl", submitCommentsUrl);
                model.asMap().clear();

            } else {
                PublicationDAO pDao = DAOFactoryManager.getFactory().newPublicationDAO();
                Publication publication = pDao.getLatestPublication(pubType, getAllowedStates(isMembersOnlyPage));
                List<PublicationItem> publicationItems = new ArrayList<>();

                if (publication != null) {
                    publicationItems.addAll(pDao.getPublicationItems(publication, PublicationItemType.ARTIFACT));
                }
                model.addAttribute("publication", publication);
                model.addAttribute("publicationItems", publicationItems);
                model.addAttribute("commentTypes", Arrays.asList(CommentType.values()));
                setupPublicationCheck(model, isMembersOnlyPage, pubType);
            }
            model.addAttribute("submitCommentsUrl", submitCommentsUrl);
            commentForm.setProcessForm(true);

        } catch (Throwable t) {
            log.error("Error during publication controller processing.", t);
            setErrorMessage(DEFAULT_ERROR_MESSAGE, model);
        }
        return applyCommonValues(model, targetPage);
    }

    /**
     * Returns an array of allowed publication states depending upon whether the desired
     * viewing should be publicly available or a members-only.
     * 
     * @param isMembersOnly  flag indicating whether the desired viewing is members-only
     * @return PublicationState[]
     */
    private PublicationState[] getAllowedStates(boolean isMembersOnly) {
        PublicationState[] allowedStates;

        if (isMembersOnly) {
            allowedStates = new PublicationState[] { PublicationState.MEMBER_REVIEW };

        } else {
            allowedStates = new PublicationState[] { PublicationState.PUBLIC_REVIEW, PublicationState.RELEASED };
        }
        return allowedStates;
    }

    /**
     * Adds the required model attributes for the 'publicationCheck.jsp' page verification.
     * 
     * @param model  the UI model for the current request
     * @param isMembersOnlyPage  flag indicating whether the page being viewed is a members-only page
     * @param expectedPubType  the type of publication that expected to be available 
     */
    private void setupPublicationCheck(Model model, boolean isMembersOnly, PublicationType expectedPubType) {
        model.addAttribute("isMembersOnly", isMembersOnly);
        model.addAttribute("expectedPubType", expectedPubType);
    }

    /**
     * Removes all publications assigned to the <code>MEMBER_REVIEW</code> state from
     * the given list.
     * 
     * @param specList  the list of publications to process
     */
    private void purgeMemberReviewSpecifications(List<Publication> specList) {
        Iterator<Publication> iterator = specList.iterator();

        while (iterator.hasNext()) {
            Publication p = iterator.next();

            if (p.getState() == PublicationState.MEMBER_REVIEW) {
                iterator.remove();
            }
        }
    }

    /**
     * Returns the current registrant or null if the user has not yet registered.
     * 
     * @param session  the HTTP session
     * @return Registrant
     */
    private Registrant getCurrentRegistrant(HttpSession session) {
        Long registrantId = (Long) session.getAttribute("registrantId");
        Registrant registrant = null;

        if (registrantId != null) {
            registrant = DAOFactoryManager.getFactory().newRegistrantDAO().getRegistrant(registrantId);
        }
        return registrant;
    }

    /**
     * Creates, retrieves, or updates the current/new registrant.  If no registration has
     * been created or a validation error occurs while attempting to create one, this method
     * will return null.
     * 
     * @param registrantForm  form containing all of registrant values submitted by the user
     * @param model  the UI model for the current request
     * @param session  the HTTP session
     */
    private void handleRegistrantInfo(RegistrantForm registrantForm, Model model, HttpSession session) {
        RegistrantDAO rDao = DAOFactoryManager.getFactory().newRegistrantDAO();
        Long registrantId = (Long) session.getAttribute("registrantId");
        Registrant registrant = null;

        if ((registrantId != null) && (registrantId >= 0)) {
            registrant = rDao.getRegistrant(registrantId);
            if (registrant == null)
                registrantId = null;
        }
        if (registrantForm.isProcessForm()) {
            if (registrant == null) {
                registrant = new Registrant();
                registrant.setRegistrationDate(new Date());
            }
            registrant.setLastName(StringUtils.trimString(registrantForm.getLastName()));
            registrant.setFirstName(StringUtils.trimString(registrantForm.getFirstName()));
            registrant.setTitle(StringUtils.trimString(registrantForm.getTitle()));
            registrant.setCompany(StringUtils.trimString(registrantForm.getCompany()));
            registrant.setPhone(StringUtils.trimString(registrantForm.getPhone()));
            registrant.setEmail(StringUtils.trimString(registrantForm.getEmail()));
            registrant.setOtaMember(registrantForm.getOtaMember());

            if ((registrantId == null) || (registrantId < 0)) { // persist for the first time
                try {
                    registrantId = rDao.saveRegistrant(registrant);
                    session.setAttribute("registrantId", registrantId);

                } catch (ValidationException e) {
                    addValidationErrors(e, model);
                    registrant = null;
                }
            }
        }
        model.addAttribute("registrant", registrant);
    }

    /**
     * Creates and saves a new schema comment using the information provided.
     * 
     * @param commentForm  the form used to supply the field values for the submitted comment
     * @param registrant  the web site registrant who submitted the comment
     * @param model  the UI model for the current request
     * @param session  the HTTP session
     */
    private boolean addSchemaComment(SchemaCommentForm commentForm, Registrant registrant, Model model,
            HttpSession session) {
        PublicationDAO pDao = DAOFactoryManager.getFactory().newPublicationDAO();
        CommentDAO cDao = DAOFactoryManager.getFactory().newCommentDAO();
        boolean success = false;
        try {
            PublicationItem item = (commentForm.getItemId() == null) ? null
                    : pDao.getPublicationItem(commentForm.getItemId());

            if ((commentForm.getItemId() != null) && (item == null)) {
                throw new DAOException("Unable to resolve the publication item to which this comment applies.");
            }

            SchemaComment comment = new SchemaCommentBuilder().setPublicationItem(item)
                    .setPublicationState((item == null) ? null : item.getOwner().getOwner().getState())
                    .setCommentType(commentForm.getCommentType())
                    .setCommentText(StringUtils.trimString(commentForm.getCommentText()))
                    .setSuggestedChange(StringUtils.trimString(commentForm.getSuggestedChange()))
                    .setCommentXPath(StringUtils.trimString(commentForm.getCommentXPath()))
                    .setModifyXPath(StringUtils.trimString(commentForm.getModifyXPath()))
                    .setNewAnnotations(commentForm.getNewAnnotations()).setSubmittedBy(registrant).build();

            if ((comment.getPublicationItem() != null) && (comment.getSubmittedBy() != null)) {
                cDao.saveComment(comment);
                success = true;

            } else {
                ValidationResults vResults = ModelValidator.validate(comment);

                if (vResults.hasViolations()) {
                    throw new ValidationException(vResults);
                }
            }

        } catch (ValidationException e) {
            addValidationErrors(e, model);

        } catch (Throwable t) {
            log.error("Error saving comment (please contact the site administrator).", t);
            setErrorMessage("Error saving comment (please contact the site administrator).", model);
        }
        return success;
    }

    /**
     * Creates and saves a new schema comment using the information provided.
     * 
     * @param commentForm  the form used to supply the field values for the submitted comment
     * @param registrant  the web site registrant who submitted the comment
     * @param model  the UI model for the current request
     * @param session  the HTTP session
     */
    private boolean addArtifactComment(ArtifactCommentForm commentForm, Registrant registrant, Model model,
            HttpSession session) {
        PublicationDAO pDao = DAOFactoryManager.getFactory().newPublicationDAO();
        CommentDAO cDao = DAOFactoryManager.getFactory().newCommentDAO();
        boolean success = false;
        try {
            PublicationItem item = (commentForm.getItemId() == null) ? null
                    : pDao.getPublicationItem(commentForm.getItemId());

            if ((commentForm.getItemId() != null) && (item == null)) {
                throw new DAOException("Unable to resolve the publication item to which this comment applies.");
            }
            ArtifactComment comment = new ArtifactCommentBuilder().setPublicationItem(item)
                    .setPublicationState((item == null) ? null : item.getOwner().getOwner().getState())
                    .setCommentType(commentForm.getCommentType())
                    .setCommentText(StringUtils.trimString(commentForm.getCommentText()))
                    .setSuggestedChange(StringUtils.trimString(commentForm.getSuggestedChange()))
                    .setPageNumbers(StringUtils.trimString(commentForm.getPageNumbers()))
                    .setLineNumbers(StringUtils.trimString(commentForm.getLineNumbers())).setSubmittedBy(registrant)
                    .build();

            if ((comment.getPublicationItem() != null) && (comment.getSubmittedBy() != null)) {
                cDao.saveComment(comment);
                success = true;

            } else {
                ValidationResults vResults = ModelValidator.validate(comment);

                if (vResults.hasViolations()) {
                    throw new ValidationException(vResults);
                }
            }

        } catch (ValidationException e) {
            addValidationErrors(e, model);

        } catch (Throwable t) {
            log.error("Error saving comment (please contact the site administrator).", t);
            setErrorMessage("Error saving comment (please contact the site administrator).", model);
        }
        return success;
    }

    /**
     * Returns the text of the release notes, or an empty string if they cannot be
     * retrieved.
     * 
     * @param publication  the publication for which to return release notes
     */
    private String getReleaseNotesText(Publication publication) {
        DownloadDAO dao = DAOFactoryManager.getFactory().newDownloadDAO();
        String releaseNotes;

        try (Reader reader = new InputStreamReader(dao.getReleaseNotesContent(publication))) {
            StringBuilder out = new StringBuilder();
            char[] buffer = new char[4096];
            int bytesRead;

            while ((bytesRead = reader.read(buffer, 0, buffer.length)) >= 0) {
                out.append(buffer, 0, bytesRead);
            }
            releaseNotes = out.toString().trim().replaceAll("\n", "<br>");

        } catch (Throwable t) {
            releaseNotes = "";
        }
        return releaseNotes;
    }

}