001/*
002 * #%L
003 * Netarchivesuite - common
004 * %%
005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library,
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023
024package dk.netarkivet.common.webinterface;
025
026import java.io.IOException;
027import java.net.MalformedURLException;
028import java.net.URL;
029import java.util.ArrayList;
030import java.util.LinkedHashMap;
031import java.util.List;
032import java.util.Locale;
033import java.util.Map;
034
035import javax.servlet.jsp.JspWriter;
036
037import dk.netarkivet.common.CommonSettings;
038import dk.netarkivet.common.exceptions.ArgumentNotValid;
039import dk.netarkivet.common.exceptions.IOFailure;
040import dk.netarkivet.common.utils.I18n;
041import dk.netarkivet.common.utils.Settings;
042
043/**
044 * This class holds information about one section of the site, including information about what to put in the menu
045 * sidebar and how to determine which page you're in.
046 */
047public abstract class SiteSection {
048    /** The overall human-readable name of the section. */
049    private final String mainname;
050    /** The number of pages that should be visible in the sidebar. */
051    private final int visiblePages;
052    /** The map of page names ("path" part of URL) to page titles. */
053    private final LinkedHashMap<String, String> pagesAndTitles = new LinkedHashMap<String, String>();
054    /** The top level directory this section represents. */
055    private String dirname;
056    /** The resource bundle with translations of this sitesection. */
057    private String bundle;
058    /**
059     * Extension used for JSP files, including '.' separator.
060     */
061    private static final String JSP_EXTENSION = ".jsp";
062    /**
063     * Loaded list of site sections.
064     *
065     * @see #getSections()
066     */
067    private static List<SiteSection> sections;
068
069    /**
070     * Create a new SiteSection object.
071     *
072     * @param mainname The name of the entire section used in the sidebar.
073     * @param prefix The prefix that all the JSP pages will have.
074     * @param visiblePages How many of the pages will be visible in the menu (taken from the start of the list).
075     * @param pagesAndTitles The actual pages and title-labels, without the prefix and jsp extension, involved in the
076     * section. They must be given as an array of 2-element arrays.
077     * @param dirname The top level directory this site section is deployed under.
078     * @param bundle The resource bundle with translations of this sitesection.
079     * @throws ArgumentNotValid if any of the elements of pagesAndTitles are not a 2-element array.
080     */
081    public SiteSection(String mainname, String prefix, int visiblePages, String[][] pagesAndTitles, String dirname,
082            String bundle) {
083        ArgumentNotValid.checkNotNullOrEmpty(mainname, "mainname");
084        ArgumentNotValid.checkNotNullOrEmpty(prefix, "prefix");
085        ArgumentNotValid.checkNotNegative(visiblePages, "visiblePages");
086        ArgumentNotValid.checkNotNull(pagesAndTitles, "String[][] pagesAndTitles");
087        ArgumentNotValid.checkNotNullOrEmpty(dirname, "dirname");
088        ArgumentNotValid.checkNotNull(bundle, "String bundle");
089        this.dirname = dirname;
090        this.mainname = mainname;
091        this.visiblePages = visiblePages;
092        this.bundle = bundle;
093        for (String[] pageAndTitle : pagesAndTitles) {
094            if (pageAndTitle.length != 2) {
095                throw new ArgumentNotValid("Must have exactly page and title in " + prefix);
096            }
097            this.pagesAndTitles.put(prefix + "-" + pageAndTitle[0] + JSP_EXTENSION, pageAndTitle[1]);
098        }
099    }
100
101    /**
102     * Given a URL, returns the corresponding page title.
103     *
104     * @param url a given URL.
105     * @param locale the current locale.
106     * @return the corresponding page title, or null if it is not in this section, or is null.
107     * @throws ArgumentNotValid on null locale.
108     */
109    public String getTitle(String url, Locale locale) {
110        ArgumentNotValid.checkNotNull(locale, "Locale locale");
111        String page = getPage(url);
112        if (page == null) {
113            return null;
114        }
115        String label = pagesAndTitles.get(page);
116        if (label == null) {
117            return null;
118        } else {
119            return I18n.getString(bundle, locale, label);
120        }
121    }
122
123    /**
124     * Generate this section's part of the navigation tree (sidebar). This outputs balanced HTML to the JspWriter. It
125     * uses a locale to generate the right titles.
126     *
127     * @param out A place to write our HTML
128     * @param url The url of the page we're currently viewing. The list of subpages will only be displayed if the page
129     * we're viewing is one that belongs to this section.
130     * @param locale The locale to generate the navigation tree for.
131     * @throws IOException If there is a problem writing to the page.
132     */
133    public void generateNavigationTree(JspWriter out, String url, Locale locale) throws IOException {
134        String firstPage = pagesAndTitles.keySet().iterator().next();
135        out.print("<tr>");
136        out.print("<td><a href=\"/" + HTMLUtils.encode(dirname) + "/" + HTMLUtils.encode(firstPage) + "\">"
137                + HTMLUtils.escapeHtmlValues(I18n.getString(bundle, locale, mainname)) + "</a></td>\n");
138        out.print("</tr>");
139        // If we are on the above page or one of its subpages, display the
140        // next level down in the tree
141        String page = getPage(url);
142        if (page == null) {
143            return;
144        }
145        if (pagesAndTitles.containsKey(page)) {
146            int i = 0;
147            for (Map.Entry<String, String> pageAndTitle : pagesAndTitles.entrySet()) {
148                if (i == visiblePages) {
149                    break;
150                }
151                out.print("<tr>");
152                out.print("<td>&nbsp; &nbsp; <a href=\"/" + HTMLUtils.encode(dirname) + "/"
153                        + HTMLUtils.encode(pageAndTitle.getKey()) + "\"> "
154                        + HTMLUtils.escapeHtmlValues(I18n.getString(bundle, locale, pageAndTitle.getValue()))
155                        + "</a></td>");
156                out.print("</tr>\n");
157                i++;
158            }
159            if (this.getClass().getName().equalsIgnoreCase("dk.netarkivet.harvester.webinterface.HistorySiteSection")) {
160                out.print("<tr>");
161                out.print("<td>&nbsp; &nbsp; <a href=\"/" + HTMLUtils.encode(dirname) + "/"
162                        + HTMLUtils.encode("history") + "/\"> "
163                        + HTMLUtils.escapeHtmlValues(I18n.getString(bundle, locale, "H3 remote access"))
164                        + "</a></td>");
165                out.print("</tr>\n");
166            }
167        }
168    }
169
170    /**
171     * Returns the page name from a URL, if the page is in this hierarchy, null otherwise.
172     *
173     * @param url Url to check
174     * @return Page name, or null for not in this hierarchy.
175     */
176    private String getPage(String url) {
177        URL parsed = null;
178        try {
179            parsed = new URL(url);
180        } catch (MalformedURLException e) {
181            return null;
182        } catch (NullPointerException e) {
183            return null;
184        }
185        String path = parsed.getPath();
186        String page;
187        int index = path.lastIndexOf(dirname + "/");
188        if (index == -1) {
189            return null;
190        }
191        page = path.substring(index + dirname.length() + 1);
192        return page;
193    }
194
195    /**
196     * Return the directory name of this site section.
197     *
198     * @return The dirname.
199     */
200    public String getDirname() {
201        return dirname;
202    }
203
204    /**
205     * Called when the site section is first deployed. Meant to be overridden by subclasses.
206     */
207    public abstract void initialize();
208
209    /**
210     * Called when webserver shuts down. Meant to be overridden by subclasses.
211     */
212    public abstract void close();
213
214    /**
215     * The list of sections of the website. Each section has a number of pages, as defined in the sitesection classes
216     * read from settings. These handle outputting their HTML part of the sidebar, depending on where in the site we
217     * are.
218     *
219     * @return A list of site sections instantiated from settings.
220     * @throws IOFailure if site sections cannot be read from settings.
221     */
222    public static synchronized List<SiteSection> getSections() {
223        if (sections == null) {
224            sections = new ArrayList<>();
225            String[] sitesections = Settings.getAll(CommonSettings.SITESECTION_CLASS);
226            for (String sitesection : sitesections) {
227                try {
228                    ClassLoader loader = SiteSection.class.getClassLoader();
229                    sections.add((SiteSection) loader.loadClass(sitesection).newInstance());
230                } catch (Exception e) {
231                    throw new IOFailure("Cannot read site section from settings", e);
232                }
233            }
234        }
235        return sections;
236    }
237
238    /**
239     * Clean up sitesections. This method calls close on all deployed site sections, and resets the list of site
240     * sections.
241     */
242    public static synchronized void cleanup() {
243        if (sections != null) {
244            for (SiteSection section : sections) {
245                section.close();
246            }
247        }
248        sections = null;
249
250    }
251
252    /**
253     * Check whether a section with a given dirName is deployed.
254     *
255     * @param dirName The dirName to check for
256     * @return True of deployed, false otherwise.
257     * @throws ArgumentNotValid if dirName is null or empty.
258     */
259    public static boolean isDeployed(String dirName) {
260        ArgumentNotValid.checkNotNullOrEmpty(dirName, "String dirName");
261        boolean sectionDeployed = false;
262        for (SiteSection section : getSections()) {
263            if (section.getDirname().equals(dirName)) {
264                sectionDeployed = true;
265            }
266        }
267        return sectionDeployed;
268    }
269}