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> <a href=\"/" + HTMLUtils.encode(dirname) + "/" 153 + HTMLUtils.encode(pageAndTitle.getKey()) + "\"> " 154 + HTMLUtils.escapeHtmlValues(I18n.getString(bundle, locale, pageAndTitle.getValue())) 155 + "</a></td>\n"); 156 out.print("</tr>"); 157 i++; 158 } 159 } 160 } 161 162 /** 163 * Returns the page name from a URL, if the page is in this hierarchy, null otherwise. 164 * 165 * @param url Url to check 166 * @return Page name, or null for not in this hierarchy. 167 */ 168 private String getPage(String url) { 169 URL parsed = null; 170 try { 171 parsed = new URL(url); 172 } catch (MalformedURLException e) { 173 return null; 174 } catch (NullPointerException e) { 175 return null; 176 } 177 String path = parsed.getPath(); 178 String page; 179 int index = path.lastIndexOf(dirname + "/"); 180 if (index == -1) { 181 return null; 182 } 183 page = path.substring(index + dirname.length() + 1); 184 return page; 185 } 186 187 /** 188 * Return the directory name of this site section. 189 * 190 * @return The dirname. 191 */ 192 public String getDirname() { 193 return dirname; 194 } 195 196 /** 197 * Called when the site section is first deployed. Meant to be overridden by subclasses. 198 */ 199 public abstract void initialize(); 200 201 /** 202 * Called when webserver shuts down. Meant to be overridden by subclasses. 203 */ 204 public abstract void close(); 205 206 /** 207 * The list of sections of the website. Each section has a number of pages, as defined in the sitesection classes 208 * read from settings. These handle outputting their HTML part of the sidebar, depending on where in the site we 209 * are. 210 * 211 * @return A list of site sections instantiated from settings. 212 * @throws IOFailure if site sections cannot be read from settings. 213 */ 214 public static synchronized List<SiteSection> getSections() { 215 if (sections == null) { 216 sections = new ArrayList<>(); 217 String[] sitesections = Settings.getAll(CommonSettings.SITESECTION_CLASS); 218 for (String sitesection : sitesections) { 219 try { 220 ClassLoader loader = SiteSection.class.getClassLoader(); 221 sections.add((SiteSection) loader.loadClass(sitesection).newInstance()); 222 } catch (Exception e) { 223 throw new IOFailure("Cannot read site section from settings", e); 224 } 225 } 226 } 227 return sections; 228 } 229 230 /** 231 * Clean up sitesections. This method calls close on all deployed site sections, and resets the list of site 232 * sections. 233 */ 234 public static synchronized void cleanup() { 235 if (sections != null) { 236 for (SiteSection section : sections) { 237 section.close(); 238 } 239 } 240 sections = null; 241 242 } 243 244 /** 245 * Check whether a section with a given dirName is deployed. 246 * 247 * @param dirName The dirName to check for 248 * @return True of deployed, false otherwise. 249 * @throws ArgumentNotValid if dirName is null or empty. 250 */ 251 public static boolean isDeployed(String dirName) { 252 ArgumentNotValid.checkNotNullOrEmpty(dirName, "String dirName"); 253 boolean sectionDeployed = false; 254 for (SiteSection section : getSections()) { 255 if (section.getDirname().equals(dirName)) { 256 sectionDeployed = true; 257 } 258 } 259 return sectionDeployed; 260 } 261}