com.aurel.track.report.datasource.faqs.FaqsDatasource.java Source code

Java tutorial

Introduction

Here is the source code for com.aurel.track.report.datasource.faqs.FaqsDatasource.java

Source

/**
 * Genji Scrum Tool and Issue Tracker
 * Copyright (C) 2015 Steinbeis GmbH & Co. KG Task Management Solutions
    
 * <a href="http://www.trackplus.com">Genji Scrum Tool</a>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/* $Id:$ */

package com.aurel.track.report.datasource.faqs;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.zip.ZipOutputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.aurel.track.admin.customize.category.filter.TreeFilterFacade;
import com.aurel.track.admin.customize.category.filter.execute.FilterExecuterFacade;
import com.aurel.track.admin.customize.category.filter.execute.loadItems.FilterUpperConfigUtil;
import com.aurel.track.admin.customize.category.filter.execute.loadItems.LoadItemIDListItems;
import com.aurel.track.admin.customize.category.filter.execute.loadItems.LoadTreeFilterItems;
import com.aurel.track.admin.customize.category.filter.execute.loadItems.ReportBeanLoader;
import com.aurel.track.admin.customize.category.filter.execute.loadItems.TooManyItemsToLoadException;
import com.aurel.track.admin.customize.category.filter.tree.design.FilterUpperTO;
import com.aurel.track.admin.customize.treeConfig.field.FieldBL;
import com.aurel.track.admin.project.ProjectBL;
import com.aurel.track.attachment.AttachBL;
import com.aurel.track.beans.ILabelBean;
import com.aurel.track.beans.TFieldBean;
import com.aurel.track.beans.TPersonBean;
import com.aurel.track.beans.TProjectBean;
import com.aurel.track.beans.TWorkItemBean;
import com.aurel.track.dbase.HandleHome;
import com.aurel.track.errors.ErrorData;
import com.aurel.track.fieldType.constants.SystemFields;
import com.aurel.track.fieldType.runtime.base.IFieldTypeRT;
import com.aurel.track.fieldType.runtime.base.LookupContainer;
import com.aurel.track.fieldType.types.FieldTypeManager;
import com.aurel.track.itemNavigator.ItemNavigatorBL;
import com.aurel.track.itemNavigator.ItemNavigatorBL.QUERY_TYPE;
import com.aurel.track.itemNavigator.QueryContext;
import com.aurel.track.lucene.util.FileUtil;
import com.aurel.track.plugin.DatasourceDescriptor;
import com.aurel.track.prop.ApplicationBean;
import com.aurel.track.report.datasource.IPluggableDatasource;
import com.aurel.track.report.datasource.ReportBeanDatasource;
import com.aurel.track.report.execute.ReportBean;
import com.aurel.track.report.execute.ReportBeans;
import com.aurel.track.util.GeneralUtils;
import com.aurel.track.util.HTMLSanitiser;
import com.aurel.track.util.LabelValueBean;
import com.aurel.track.util.TreeNode;
import com.aurel.track.util.emailHandling.Html2Text;
import com.xmlmind.util.Console;
import com.xmlmind.whc.Compiler;

public class FaqsDatasource extends ReportBeanDatasource {

    private static final Logger LOGGER = LogManager.getLogger(FaqsDatasource.class);
    private static String FAQ_DIR = null;
    private static String CRLF = System.getProperty("line.separator");
    private static String header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + CRLF
            + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
            + CRLF + "<html xmlns=\"http://www.w3.org/1999/xhtml\">" + CRLF + "    <head>";
    //        <title>FAQ: The title here</title>         
    private static String styles = "        <style type=\"text/css\">body{font-family:Helvetica,Arial,Tahoma,SansSerif}"
            + CRLF + "            h1{font-weight: bold; font-size:12pt}" + CRLF + "            .faq{font-size:10pt}"
            + CRLF + "            .bottom{margin-top:2em}</style>" + CRLF
            + "        <link href=\"styles.css\" rel=\"stylesheet\" type=\"text/css\" />" + CRLF + "    </head>"
            + CRLF + "    <body>" + CRLF;

    private static String footer = "        <div class=\"bottom\" />" + CRLF + "    </body>" + CRLF + "</html>";

    /**
     * Gets the data source object (an XHTML Document object in this case) retrieved using the parameters settings
     * @param parameters
     * @param datasourceDescriptor
     * @param contextMap
     * @param templateDescriptionMap
     * @param templateID
     * @param personBean
     * @param locale
     * @return
     * @throws TooManyItemsToLoadException 
     */
    @Override
    public Object getDatasource(Map<String, String[]> parameters, DatasourceDescriptor datasourceDescriptor,
            Map<String, Object> contextMap, Map<String, Object> templateDescriptionMap, Integer templateID,
            TPersonBean personBean, Locale locale) throws TooManyItemsToLoadException {
        ReportBeans reportBeans = null;
        Boolean fromIssueNavigator = (Boolean) contextMap.get(CONTEXT_ATTRIBUTE.FROM_ISSUE_NAVIGATOR);
        Integer dashboardProjectOrReleaseID = (Integer) contextMap
                .get(CONTEXT_ATTRIBUTE.DASHBOARD_PROJECT_RELEASE_ID);
        boolean useProjectSpecificID = false;
        Boolean projectSpecificID = (Boolean) contextMap
                .get(IPluggableDatasource.CONTEXT_ATTRIBUTE.USE_PROJETC_SPECIFIC_ID);
        if (projectSpecificID != null) {
            useProjectSpecificID = projectSpecificID.booleanValue();
        }
        String queryName = null;
        String queryExpression = null;
        if (fromIssueNavigator != null && fromIssueNavigator.booleanValue()) {
            //from issue navigator: datasource is the last query
            List<Integer> workItemIDs = (List<Integer>) contextMap.get(CONTEXT_ATTRIBUTE.WORKITEMIDS);
            if (workItemIDs != null && !workItemIDs.isEmpty()) {
                List<ReportBean> reportBeanList = LoadItemIDListItems.getReportBeansByWorkItemIDs(
                        GeneralUtils.createIntArrFromIntegerList(workItemIDs), false, personBean.getObjectID(),
                        locale, false, false, false, false, false, false, false, false, false);
                reportBeans = new ReportBeans(reportBeanList, locale, null, false);
            } else {
                /*Integer workItemID = (Integer)contextMap.get(CONTEXT_ATTRIBUTE.WORKITEMID);
                if (workItemID==null) {*/
                QueryContext queryContext = ItemNavigatorBL.loadLastQuery(personBean.getObjectID(), locale);
                if (queryContext != null) {
                    reportBeans = ItemNavigatorBL.executeQuery(personBean, locale, queryContext, null, false);
                    queryName = queryContext.getQueryName();
                    contextMap.put(CONTEXT_ATTRIBUTE.QUERY_ID, queryContext.getQueryID());
                    contextMap.put(CONTEXT_ATTRIBUTE.QUERY_TYPE, queryContext.getQueryType());
                }
                /*} else {
                   List<ReportBean> reportBeanList = ReportBeanLoader.getReportBeansByWorkItemIDsNoFilterNoLink(new int[] {workItemID}, personBean.getObjectID(), locale);
                   reportBeans= new ReportBeans(reportBeanList, personBean.getObjectID(), locale, null, false);
                }*/
            }
        } else {
            if (dashboardProjectOrReleaseID != null) {
                //from dashboard: the project/release of the project/release specific dashboard
                //or the project/release set as datasource for "global" dashboard
                Integer dashboardID = (Integer) contextMap.get(CONTEXT_ATTRIBUTE.DASHBOARD_ID);
                queryName = ItemNavigatorBL.getQueryName(QUERY_TYPE.DASHBOARD, dashboardID, -1, locale);
                FilterUpperTO filterUpperTO = FilterUpperConfigUtil
                        .getByProjectReleaseID(dashboardProjectOrReleaseID, true, true, false);
                List<ReportBean> reportBeanList = LoadTreeFilterItems.getTreeFilterReportBeans(filterUpperTO, null,
                        null, personBean, locale);
                reportBeans = new ReportBeans(reportBeanList, locale, null, true);
                contextMap.put(CONTEXT_ATTRIBUTE.QUERY_ID, dashboardProjectOrReleaseID);
                contextMap.put(CONTEXT_ATTRIBUTE.QUERY_TYPE, ItemNavigatorBL.QUERY_TYPE.DASHBOARD);
            } else {
                //from report configuration page: first save the parameters into the database, then get the reportBeans
                Map<String, Object> savedParamsMap = new HashMap<String, Object>();
                String paramSettings = loadParamObjectsAndPropertyStringsAndFromStringArrParams(parameters, locale,
                        savedParamsMap);
                saveParameters(paramSettings, personBean.getObjectID(), templateID);
                Integer datasourceType = (Integer) savedParamsMap.get(PARAMETER_NAME.DATASOURCETYPE);
                if (datasourceType == null) {
                    LOGGER.warn("No datasourceType was selected");
                    return null;
                }
                if (datasourceType.intValue() == DATASOURCE_TYPE.PROJECT_RELEASE) {
                    Integer projectOrReleaseID = (Integer) savedParamsMap.get(PARAMETER_NAME.PROJECT_OR_RELEASE_ID);
                    if (projectOrReleaseID == null) {
                        LOGGER.warn("No project/release was selected");
                        return null;
                    } else {
                        FilterUpperTO filterUpperTO = FilterUpperConfigUtil
                                .getByProjectReleaseID(projectOrReleaseID, true, true, false);
                        List<ReportBean> reportBeanList = LoadTreeFilterItems
                                .getTreeFilterReportBeans(filterUpperTO, null, null, personBean, locale);
                        reportBeans = new ReportBeans(reportBeanList, locale, null, true);
                        queryName = ItemNavigatorBL.getQueryName(QUERY_TYPE.PROJECT_RELEASE, projectOrReleaseID, -1,
                                locale);
                        contextMap.put(CONTEXT_ATTRIBUTE.QUERY_ID, projectOrReleaseID);
                        contextMap.put(CONTEXT_ATTRIBUTE.QUERY_TYPE, ItemNavigatorBL.QUERY_TYPE.PROJECT_RELEASE);
                    }
                } else {
                    Integer filterID = (Integer) savedParamsMap.get(PARAMETER_NAME.FILTER_ID);
                    if (filterID == null) {
                        LOGGER.warn("No filter was selected");
                        return null;
                    } else {
                        reportBeans = FilterExecuterFacade.getSavedFilterReportBeans(filterID, locale, personBean,
                                new LinkedList<ErrorData>(), null, null, null);
                        contextMap.put(CONTEXT_ATTRIBUTE.QUERY_ID, filterID);
                        contextMap.put(CONTEXT_ATTRIBUTE.QUERY_TYPE, ItemNavigatorBL.QUERY_TYPE.SAVED);
                        ILabelBean labelBean = TreeFilterFacade.getInstance().getByKey(filterID);
                        if (labelBean != null) {
                            queryName = labelBean.getLabel();
                        }
                    }
                }
            }
        }

        boolean withHistory = false;
        boolean longTextIsPlain = false;

        return getDocumentFromReportBeans(reportBeans, withHistory, personBean, locale, queryName, queryExpression,
                longTextIsPlain, useProjectSpecificID);
    }

    /**
     * Transform the reportBeans into DOM Document
     * @return
     */
    protected static Document getDocumentFromReportBeans(ReportBeans reportBeans, boolean withHistory,
            TPersonBean personBean, Locale locale, String queryName, String queryExpression,
            boolean longTextIsPlain, boolean useProjectSpecificID) {
        if (reportBeans == null) {
            return null;
        }
        personBean.getObjectID();
        List<ReportBean> items = reportBeans.getItems();
        if (items == null) {
            return null;
        }
        ReportBeanLoader.addISOValuesToReportBeans(items, personBean.getObjectID(), locale);
        if (longTextIsPlain) {
        }
        //add the history to each ReportBean: the long fields history elements can be transformed to the longTextType
        //needed and consequently we can spare storing also the real attribute values in the HistoryValues objects
        //(near the showValues) but the actual long fields cannot be transformed at this moment because 
        //they are stored in the session and it would affect the future renderings of the ReportBeans

        ReportBeansToXHTMLConverter rc = new FaqsDatasource().new ReportBeansToXHTMLConverter();

        //if (personBean.isSysAdmin() || personBean.isSysManager()) { // write to disk
        rc.writeFiles(items, withHistory, locale, personBean.getFullName(), queryName, queryExpression,
                useProjectSpecificID);
        //}

        return rc.convertToDOM(items, withHistory, locale, personBean.getFullName(), queryName, queryExpression,
                useProjectSpecificID);

    }

    /**
     * @param items
     * @return
     */
    public static void convertToXml(OutputStream outputStream, Document dom) {
        Transformer transformer = null;
        try {
            TransformerFactory factory = TransformerFactory.newInstance();
            transformer = factory.newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
            transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "-//W3C//DTD XHTML 1.0 Transitional//EN");
            transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
                    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd");
        } catch (TransformerConfigurationException e) {
            LOGGER.error(
                    "Creating the transformer failed with TransformerConfigurationException: " + e.getMessage());
            return;
        }
        try {
            transformer.transform(new DOMSource(dom), new StreamResult(outputStream));
        } catch (TransformerException e) {
            LOGGER.error("Transform failed with TransformerException: " + e.getMessage());
        }
    }

    /**
     * @param os
     * write the zipped FAQ directory into the download output stream
     */
    public static void streamZippedFAQs(OutputStream os) {
        try {
            File tmpDir = FileUtils.getTempDirectory();
            File zipFile = new File(tmpDir.getAbsolutePath() + File.separator + "FAQs.zip");

            try {
                //FileUtils.forceDelete(zipFile);
                zipFile.delete();
            } catch (/*FileNotFound*/Exception fne) {
                // ignore, that is okay
            }

            FileOutputStream fos = new FileOutputStream(zipFile);
            ZipOutputStream zos = new ZipOutputStream(fos);
            FileUtil.zipFiles(new File(FAQ_DIR), zos);
            zos.close();
            fos.close();

            InputStream is = new FileInputStream(zipFile);
            IOUtils.copy(is, os);
            //FileUtils.forceDelete(zipFile);
            zipFile.delete();
            is.close();
        } catch (FileNotFoundException e) {
            LOGGER.error(ExceptionUtils.getStackTrace(e));
        } catch (Exception e) {
            LOGGER.error(ExceptionUtils.getStackTrace(e));

        }
    }

    /**
     * Inner class to create the FAQ help file structure.
     *
     */
    class ReportBeansToXHTMLConverter implements Console {

        private String FAQ_TMP_DIR = null;

        public Document convertToDOM(List<ReportBean> items, boolean withHistory, Locale locale, String personName,
                String filterName, String filterExpression, boolean useProjectSpecificID) {
            Document dom = null;
            try {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = factory.newDocumentBuilder();
                dom = builder.newDocument();
            } catch (FactoryConfigurationError e) {
                LOGGER.error("Creating the DOM document failed with FactoryConfigurationError:" + e.getMessage());
                return null;
            } catch (ParserConfigurationException e) {
                LOGGER.error(
                        "Creating the DOM document failed with ParserConfigurationException: " + e.getMessage());
                return null;
            }
            return dom;
        }

        /**
         * @param items
         * @param withHistory
         * @param locale
         * @param fullName
         * @param queryName
         * @param queryExpression
         * @param useProjectSpecificID
         */
        protected void writeFiles(List<ReportBean> items, boolean withHistory, Locale locale, String fullName,
                String queryName, String queryExpression, boolean useProjectSpecificID) {

            List<TFieldBean> fields = FieldBL.loadAll();
            FAQ_DIR = HandleHome.getTrackplus_Home() + File.separator + "Faqs";
            Long stamp = new Date().getTime();
            String sstamp = stamp.toString();
            FAQ_TMP_DIR = HandleHome.getTrackplus_Home() + File.separator + "Faqs" + File.separator + "tmp"
                    + sstamp;
            HashMap<String, TWorkItemBean> fileNameMap = new HashMap<String, TWorkItemBean>();
            HashMap<Integer, String> workItemMap = new HashMap<Integer, String>();

            String title = null;
            String body = null;

            File faqDir = null;
            File faqOutDir = null;
            try {
                faqDir = new File(FAQ_DIR);
                faqOutDir = new File(FAQ_TMP_DIR);
                if (!faqDir.exists() || !faqDir.isDirectory()) {
                    Boolean success = faqDir.mkdirs();
                    if (!success) {
                        LOGGER.error("Could not create FAQ directory at " + FAQ_DIR);
                    }
                }
                if (!faqOutDir.exists() || !faqOutDir.isDirectory()) {
                    Boolean success = faqOutDir.mkdirs();
                    if (!success) {
                        LOGGER.error("Could not create FAQ out directory at " + FAQ_TMP_DIR);
                    }
                }
                cleanDirectory(FAQ_DIR, ".*\\.html");
            } catch (Exception e) {
                LOGGER.error(e.getMessage());
            }

            for (ReportBean reportBean : items) {

                body = "";
                // Element item = dom.createElement ("item");
                TWorkItemBean workItemBean = reportBean.getWorkItemBean();
                String ptitle = workItemBean.getSynopsis();
                ptitle = StringEscapeUtils.escapeHtml(ptitle);
                ptitle = "<title>FAQ: " + ptitle + "</title>";

                String fileName = null;
                TProjectBean project = LookupContainer.getProjectBean(workItemBean.getProjectID());
                String prefix = null;
                if (project != null) {
                    prefix = project.getPrefix();
                }
                if (prefix == null) {
                    prefix = "";
                }
                for (TFieldBean fieldBean : fields) {
                    Integer fieldID = fieldBean.getObjectID();
                    if (ApplicationBean.getInstance().getSiteBean().getProjectSpecificIDsOn()) {
                        if (fieldID == SystemFields.PROJECT_SPECIFIC_ISSUENO) {
                            Object attributeValue = workItemBean.getAttribute(fieldID);
                            if (attributeValue != null && !"null".equals(attributeValue)) {
                                fileName = prefix + attributeValue.toString();
                            }
                        }
                    } else {
                        if (fieldID == SystemFields.ISSUENO) {
                            Object attributeValue = workItemBean.getAttribute(fieldID);
                            if (attributeValue != null && attributeValue != null && !attributeValue.equals("")
                                    && !"null".equals(attributeValue)) {
                                fileName = attributeValue.toString();
                            }
                        }
                    }
                    if (fieldID == SystemFields.PROJECT) {
                        Object attributeValue = workItemBean.getAttribute(fieldID);
                        if (attributeValue != null) {
                        }
                    }
                    fieldBean.getName();
                    Object attributeValue = workItemBean.getAttribute(fieldID);
                    if (fieldID == SystemFields.SYNOPSIS || fieldID == SystemFields.DESCRIPTION) {
                        if (attributeValue != null) {
                            IFieldTypeRT fieldTypeRT = FieldTypeManager.getFieldTypeRT(fieldID);
                            if (fieldTypeRT != null) {
                                if (!fieldTypeRT.isLong()) {
                                    title = (String) attributeValue;
                                    title = StringEscapeUtils.escapeHtml(title);
                                    title = "<h1>" + title + "</h1>";
                                } else {
                                    body = workItemBean.getDescription();
                                    body = HTMLSanitiser.stripHTML(body);
                                    body = "<div class=\"faq\">" + body + "</div>";
                                }
                            }
                        }
                    }
                }

                StringBuffer buf = new StringBuffer();
                buf.append(header);
                buf.append(ptitle);
                buf.append(styles);
                buf.append(title);
                buf.append(body);
                buf.append(footer);

                fileName = fileName + ".html";

                URL url = writeFile(buf.toString(), fileName);
                if (url != null) {
                    fileNameMap.put(fileName, workItemBean);
                    workItemMap.put(workItemBean.getObjectID(), fileName);
                }
            }

            Map<Integer, List<TWorkItemBean>> projectMap = new HashMap<Integer, List<TWorkItemBean>>();
            Set<Integer> projectIDs = new HashSet<Integer>();
            for (Entry<String, TWorkItemBean> entry : fileNameMap.entrySet()) {
                TWorkItemBean workItemBean = entry.getValue();
                Integer projectID = workItemBean.getProjectID();
                if (!projectMap.containsKey(projectID)) {
                    projectMap.put(projectID, new ArrayList<TWorkItemBean>());
                    projectIDs.add(projectID);
                }
                List<TWorkItemBean> tmp = projectMap.get(projectID);
                tmp.add(workItemBean);
                projectMap.put(projectID, tmp);
            }

            //List<TProjectBean> projectBeanList = ProjectBL.loadByProjectIDs(projectIDs);
            LOGGER.info(projectIDs.size() + " workspaces for with items for FAQs");
            List<TreeNode> projectBeanTree = ProjectBL.getProjectTreeForReleasesWithCompletedPath(
                    GeneralUtils.createIntegerListFromCollection(projectIDs), false, null, false, false, false,
                    new TreeMap<Integer, TreeNode>());
            List<LabelValueBean> projects = new LinkedList<LabelValueBean>();
            loadAllInvolvedProjects(projectBeanTree, projects);
            LOGGER.info(projects.size() + " involved workspaces for FAQs.");

            // Create table of content from projects and workitems
            createTOC(projectBeanTree, projectMap, workItemMap);

            // Create the FAQ files from workitems
            File[] files = new File[fileNameMap.size() + projects.size() + 1];

            int i = 0;
            for (String faq : fileNameMap.keySet()) {
                try {
                    files[i] = new File(FAQ_TMP_DIR + File.separator + faq);
                } catch (Exception e) {
                    LOGGER.error("Problem creating file array for FAQs: " + e.getMessage());
                }
                ++i;
            }

            // Create a file for each project
            for (LabelValueBean proj : projects) {
                try {
                    String fname = "00PROJECT" + proj.getValue() + ".html";
                    File projFile = new File(FAQ_TMP_DIR + File.separator + fname);
                    createProjFile(fname, proj.getLabel());
                    files[i] = projFile;
                } catch (Exception e) {
                    LOGGER.error("Problem creating file array for FAQs: " + e.getMessage());
                }
                ++i;
            }

            // Create the empty index.html file that will contain just the navigator
            createIndexHtmlFile("index.html");

            files[i] = new File(FAQ_TMP_DIR + File.separator + "index.html");

            createWebHelp(files);

            AttachBL.deleteDirectory(new File(FAQ_TMP_DIR));

            /*try {
               FileUtils.deleteDirectory(new File(FAQ_TMP_DIR));
            } catch (Exception e) {
               LOGGER.error("Can't delete temporary FAQ directory: " + e.getMessage());
            }*/

            return;
        }

        /**
         * Loads all projectNodes from the tree into projects list 
         * @param projectBeanTree
         * @param projects output parameter
         */
        private void loadAllInvolvedProjects(List<TreeNode> projectBeanTree, List<LabelValueBean> projects) {
            for (TreeNode projectNode : projectBeanTree) {
                projects.add(new LabelValueBean(projectNode.getLabel(), projectNode.getId()));
                List<TreeNode> children = projectNode.getChildren();
                if (children != null && !children.isEmpty()) {
                    loadAllInvolvedProjects(children, projects);
                }
            }
        }

        /**
         * @param text 
         * @param fileName
         * @return
         */
        public URL writeFile(String text, String fileName) {
            OutputStream outputStream = null;
            URL url = null;
            try {
                byte data[] = text.getBytes("UTF-8");
                File out = new File(FAQ_TMP_DIR + File.separator + fileName);
                outputStream = new FileOutputStream(out);
                outputStream.write(data, 0, data.length);
                url = out.toURI().toURL();
                LOGGER.debug("Wrote FAQ file " + out.getAbsolutePath());
            } catch (Exception e) {
                LOGGER.error("Writing FAQs failed with exception: " + e.getMessage());
            } finally {
                try {
                    // Close the stream regardless of what happens...
                    outputStream.close();
                } catch (Exception e) {
                    LOGGER.error("Could not close output stream for " + fileName);
                }
            }
            return url;
        }

        /**
         * @param dom
         * @param fileName
         * @return
         */
        public URL writeFile(Document dom, String fileName) {
            return writeFile(dom, fileName, "-//W3C//DTD XHTML 1.0 Strict//EN",
                    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd", "xhtml");
        }

        /**
         * @param dom
         * @param fileName
         * @param publicID
         * @param systemID
         * @return
         */
        private URL writeFile(Document dom, String fileName, String publicID, String systemID, String method) {
            Transformer transformer = null;
            try {
                TransformerFactory factory = TransformerFactory.newInstance();
                transformer = factory.newTransformer();
                transformer.setOutputProperty(OutputKeys.INDENT, "yes");
                transformer.setOutputProperty(OutputKeys.METHOD, method);
                transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
                transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
                if (publicID != null) {
                    transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, publicID);
                }
                if (systemID != null) {
                    transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, systemID);
                }
            } catch (TransformerConfigurationException e) {
                LOGGER.error("Creating the transformer failed with TransformerConfigurationException: "
                        + e.getMessage());
                return null;
            }
            StreamResult outputStream = null;
            try {
                File out = new File(FAQ_TMP_DIR + File.separator + fileName);
                outputStream = new StreamResult(out.toURI().getPath());
                transformer.transform(new DOMSource(dom), outputStream);
                URL url = out.toURI().toURL();
                LOGGER.debug("Wrote FAQ file " + out.getAbsolutePath());
                return url;
            } catch (TransformerException e) {
                LOGGER.error("Transform failed with TransformerException: " + e.getMessage());
            } catch (Exception ex) {
                LOGGER.error("Transform failed with exception: " + ex.getMessage());
            }
            return null;
        }

        /**
         * @param projectNodes
         * @param projectMap
         */
        protected void createTOC(List<TreeNode> projectNodes, Map<Integer, List<TWorkItemBean>> projectMap,
                Map<Integer, String> workItemMap) {
            Document dom = null;
            try {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = factory.newDocumentBuilder();
                dom = builder.newDocument();
            } catch (FactoryConfigurationError e) {
                LOGGER.error("Creating the DOM document failed with FactoryConfigurationError:" + e.getMessage());
                return;
            } catch (ParserConfigurationException e) {
                LOGGER.error(
                        "Creating the DOM document failed with ParserConfigurationException: " + e.getMessage());
                return;
            }

            Element model = dom.createElement("xml-model");
            model.setAttribute("href", "schema/toc.rnc");
            model.setAttribute("type", "application/relax-ng-compact-syntax");

            Element root = dom.createElement("whc:toc");
            root.setAttribute("xmlns:whc", "http://www.xmlmind.com/whc/schema/whc");
            root.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");

            for (TreeNode projectNode : projectNodes) {
                appendProject(projectNode, projectMap, workItemMap, dom, root);
                /*Integer projecID = projectBean.getObjectID();
                Element entry = dom.createElement ("whc:entry");
                entry.setAttribute("href","00PROJECT"+projecID.toString()+".html");  //Project page
                Element title = createDomElement("whc:title",projectBean.getLabel(),dom);
                entry.appendChild(title);
                List<TWorkItemBean> faqs = projectMap.get(projecID);
                Integer count = 0;
                for (TWorkItemBean faq : faqs) {
                   ++count;
                   Element fentry = dom.createElement("whc:entry");
                   String fname = workItemMap.get(faq.getObjectID());
                   fentry.setAttribute("href", fname);
                   Element ftitle = createDomElement("whc:title",faq.getSynopsis(),dom);
                   fentry.appendChild(ftitle);
                   entry.appendChild(fentry);
                }
                root.appendChild(entry);*/
            }
            // dom.appendChild(model);
            dom.appendChild(root);

            writeFile(dom, "toc.xml", null, null, "xml");

            patchFile("toc.xml");

            writeIndexFile();
            return;
        }

        /**
         * Append a project with items and subprojects recursively
         * @param projectNode
         * @param projectMap
         * @param workItemMap
         * @param dom
         * @param parent
         */
        private void appendProject(TreeNode projectNode, Map<Integer, List<TWorkItemBean>> projectMap,
                Map<Integer, String> workItemMap, Document dom, Element parent) {
            Integer projecID = Integer.valueOf(projectNode.getId());
            Element entry = dom.createElement("whc:entry");
            entry.setAttribute("href", "00PROJECT" + projecID.toString() + ".html"); //Project page
            Element title = createDomElement("whc:title", projectNode.getLabel(), dom);
            entry.appendChild(title);
            List<TWorkItemBean> faqs = projectMap.get(projecID);
            if (faqs != null) {
                //Integer count = 0;
                for (TWorkItemBean faq : faqs) {
                    //++count;
                    Element fentry = dom.createElement("whc:entry");
                    String fname = workItemMap.get(faq.getObjectID());
                    fentry.setAttribute("href", fname);
                    Element ftitle = createDomElement("whc:title", faq.getSynopsis(), dom);
                    fentry.appendChild(ftitle);
                    entry.appendChild(fentry);
                }
            }
            List<TreeNode> childProjects = projectNode.getChildren();
            if (childProjects != null && !childProjects.isEmpty()) {
                for (TreeNode childProject : childProjects) {
                    appendProject(childProject, projectMap, workItemMap, dom, entry);
                }
            }
            parent.appendChild(entry);
        }

        /*
         * Create the project specific HTML file from a template
         */
        private void createProjFile(String file, String projectLabel) {
            File template = new File(FAQ_DIR + File.separator + "whc_template" + File.separator + "project.html");
            StringBuffer buf = new StringBuffer();
            String title = StringEscapeUtils.escapeHtml(projectLabel);
            LineIterator it = null;
            try {
                it = FileUtils.lineIterator(template, "UTF-8");
                try {
                    while (it.hasNext()) {
                        String line = it.nextLine();
                        line = line.replaceAll("\\$title", title);
                        buf.append(line + CRLF);
                    }
                } finally {
                    it.close();
                }
            } catch (Exception e) {
                LOGGER.error("Can't copy project.html template file: " + e.getMessage());
            }
            writeFile(buf.toString(), file);
        }

        /*
         * Copy the index.html template to the temporary working directory
         */
        private void createIndexHtmlFile(String file) {
            try {
                FileUtils.copyFile(
                        new File(FAQ_DIR + File.separator + "whc_template" + File.separator + "index.html"),
                        new File(FAQ_TMP_DIR + File.separator + "index.html"));
            } catch (Exception e) {
                LOGGER.error("Can't copy index.html template file: " + e.getMessage());
            }
        }

        /**
         * Creates a dom element
         * @param elementName
         * @param elementValue
         * @param dom
         * @return
         */
        private Element createDomElement(String elementName, String elementValue, Document dom) {
            Element element = null;
            try {
                try {
                    element = dom.createElement(elementName);
                } catch (DOMException e) {
                    LOGGER.warn("Creating an XML node with the element name " + elementName
                            + " failed with dom exception " + e);
                }
                if (element == null) {
                    return null;
                }
                if (elementValue == null || "".equals(elementValue.trim())) {
                    element.appendChild(dom.createTextNode(""));
                } else {
                    try {
                        element.appendChild(dom.createTextNode(Html2Text.stripNonValidXMLCharacters(elementValue)));
                    } catch (DOMException e) {
                        LOGGER.info("Creating the node for text element " + elementName + " and the value "
                                + elementValue + " failed with dom exception " + e);
                        element.appendChild(dom.createTextNode(""));
                    }
                }
            } catch (Exception e) {
                LOGGER.warn("Creating an XML node with the element name " + elementName + " failed with " + e);
            }
            return element;
        }

        @Override
        public void showMessage(String message, MessageType type) {
            if (type.equals(MessageType.ERROR)) {
                LOGGER.error(message);
            }
            if (type.equals(MessageType.DEBUG)) {
                LOGGER.debug(message);
            }
            if (type.equals(MessageType.WARNING)) {
                LOGGER.warn(message);
            }
            if (type.equals(MessageType.INFO)) {
                LOGGER.info(message);
            }
            if (type.equals(MessageType.VERBOSE)) {
                LOGGER.info(message);
            }
        }

        protected void createWebHelp(File[] xhtmlURLs) {
            Compiler compiler = new Compiler(this);

            if (LOGGER.isDebugEnabled()) {
                compiler.setVerbose(true);
            }
            compiler.setLocalJQuery(true);

            //      if (!compiler.parseParameters(params)) {
            //          LOGGER.error("Problem running WebHelp compiler");
            //      }
            try {
                File indexURL = new File(FAQ_TMP_DIR + File.separator + "index.xml");
                File tocURL = new File(FAQ_TMP_DIR + File.separator + "toc.xml");
                URL fileList = new File(FAQ_DIR + File.separator + "whc_template" + File.separator + "file.list")
                        .toURI().toURL();
                compiler.setTemplateManifest(fileList);
                if (!compiler.compile(xhtmlURLs, tocURL, indexURL, new File(FAQ_DIR))) {
                    LOGGER.error("Problem compiling WebHelp");
                }
            } catch (IOException e) {
                LOGGER.error(e.getMessage());
            }
        }

        private void writeIndexFile() {
            File out = new File(FAQ_TMP_DIR + File.separator + "index.xml");
            FileOutputStream os = null;

            try {

                os = FileUtils.openOutputStream(out);

                String dummyIndex = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                        + "<?xml-model href=\"schema/index.rnc\" "
                        + "    type=\"application/relax-ng-compact-syntax\"?>"
                        + "<index xmlns=\"http://www.xmlmind.com/whc/schema/whc\""
                        + "       xmlns:htm=\"http://www.w3.org/1999/xhtml\">" + "     <entry xml:id=\"dummy1\">"
                        + "        <term>TBD 1</term>" + "       <entry>" + "           <term>TBD 2</term>"
                        + "           <anchor href=\"page_a.html#cbf1000f\"/>" + "       </entry>" + "    </entry>"
                        + "    <entry xml:id=\"cbr1000rr\">" + "      <term>CBR1000RR</term>"
                        + "      <anchor href=\"page_a.html#cbr1000rr\"/>" + "      <seeAlso ref=\"dummy1\"/>"
                        + "    </entry>" + "</index>";

                String emptyIndex = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                        + "<?xml-model href=\"schema/index.rnc\" "
                        + "    type=\"application/relax-ng-compact-syntax\"?>"
                        + "<index xmlns=\"http://www.xmlmind.com/whc/schema/whc\""
                        + "       xmlns:htm=\"http://www.w3.org/1999/xhtml\">" + "</index>";

                os.write(emptyIndex.getBytes(Charset.forName("UTF-8")));

            } catch (Exception e) {
                LOGGER.error(e.getMessage());
            } finally {
                try {
                    os.flush();
                    os.close();
                } catch (Exception te) {
                    LOGGER.error(te.getMessage());
                }
            }
        }

        private void patchFile(String file) {
            File in = new File(FAQ_TMP_DIR + File.separator + file);
            File out = new File(FAQ_TMP_DIR + File.separator + "tmp.xml");

            FileInputStream is = null;
            FileOutputStream os = null;

            try {
                is = new FileInputStream(in.toURI().getPath()); //FileUtils.openInputStream(in);
                os = new FileOutputStream(out.toURI().getPath());//FileUtils.openOutputStream(out);
                LineIterator li = FileUtils.lineIterator(in, "UTF-8");
                int i = 0;
                while (li.hasNext()) {
                    String line = li.nextLine();
                    if (i < 5) {
                        line = line.replaceFirst("<whc:toc",
                                "<?xml-model href=\"schema/toc.rnc\" \n type=\"application/relax-ng-compact-syntax\"?> \n<whc:toc");
                    }
                    os.write(line.getBytes(Charset.forName("UTF-8")));
                    ++i;
                }
            } catch (Exception e) {
                LOGGER.error(e.getMessage());
            } finally {
                try {
                    os.flush();
                    is.close();
                    os.close();
                    in.delete();
                    //FileUtils.forceDelete(in);
                    //FileUtils.copyFile(out, in);
                } catch (Exception te) {
                    LOGGER.error(te.getMessage());
                }
                try {
                    FileUtils.copyFile(out, in);
                } catch (Exception te) {
                    LOGGER.error(te.getMessage());
                }
            }
        }

        private void cleanDirectory(String directory, String pattern) {
            File dir = new File(directory);
            if (!dir.exists()) {
                LOGGER.warn(directory + " does not exist");
                return;
            }
            String patt = pattern;

            String[] info = dir.list();
            for (int i = 0; i < info.length; i++) {
                File n = new File(directory + File.separator + info[i]);
                if (!n.isFile()) { // skip ., .., other directories, etc.
                    continue;
                }
                if (!info[i].matches(patt)) { // name doesn't match
                    continue;
                }
                LOGGER.debug("removing " + n.getPath());
                if (!n.delete())
                    LOGGER.error("Couldn't remove " + n.getPath());
            }
        }
    }

}