1d8a7afe2SAdam Hornacek /* 2d8a7afe2SAdam Hornacek * CDDL HEADER START 3d8a7afe2SAdam Hornacek * 4d8a7afe2SAdam Hornacek * The contents of this file are subject to the terms of the 5d8a7afe2SAdam Hornacek * Common Development and Distribution License (the "License"). 6d8a7afe2SAdam Hornacek * You may not use this file except in compliance with the License. 7d8a7afe2SAdam Hornacek * 8d8a7afe2SAdam Hornacek * See LICENSE.txt included in this distribution for the specific 9d8a7afe2SAdam Hornacek * language governing permissions and limitations under the License. 10d8a7afe2SAdam Hornacek * 11d8a7afe2SAdam Hornacek * When distributing Covered Code, include this CDDL HEADER in each 12d8a7afe2SAdam Hornacek * file and include the License file at LICENSE.txt. 13d8a7afe2SAdam Hornacek * If applicable, add the following below this CDDL HEADER, with the 14d8a7afe2SAdam Hornacek * fields enclosed by brackets "[]" replaced with your own identifying 15d8a7afe2SAdam Hornacek * information: Portions Copyright [yyyy] [name of copyright owner] 16d8a7afe2SAdam Hornacek * 17d8a7afe2SAdam Hornacek * CDDL HEADER END 18d8a7afe2SAdam Hornacek */ 19d8a7afe2SAdam Hornacek 20d8a7afe2SAdam Hornacek /* 21a964b78cSVladimir Kotal * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. 225d9f3aa0SAdam Hornáček * Portions Copyright (c) 2011, Jens Elkner. 235d9f3aa0SAdam Hornáček * Portions Copyright (c) 2017, 2020, Chris Fraire <cfraire@me.com>. 24d8a7afe2SAdam Hornacek */ 25d8a7afe2SAdam Hornacek package org.opengrok.web; 26d8a7afe2SAdam Hornacek 27d8a7afe2SAdam Hornacek import static org.opengrok.indexer.index.Indexer.PATH_SEPARATOR; 28d8a7afe2SAdam Hornacek import static org.opengrok.indexer.index.Indexer.PATH_SEPARATOR_STRING; 29d8a7afe2SAdam Hornacek 30d8a7afe2SAdam Hornacek import java.io.BufferedReader; 31d8a7afe2SAdam Hornacek import java.io.File; 32d8a7afe2SAdam Hornacek import java.io.FileNotFoundException; 33d8a7afe2SAdam Hornacek import java.io.IOException; 34d8a7afe2SAdam Hornacek import java.io.InputStream; 35d8a7afe2SAdam Hornacek import java.net.URI; 36d8a7afe2SAdam Hornacek import java.net.URISyntaxException; 37d8a7afe2SAdam Hornacek import java.net.URLDecoder; 38d8a7afe2SAdam Hornacek import java.nio.charset.StandardCharsets; 39d8a7afe2SAdam Hornacek import java.nio.file.Paths; 40d8a7afe2SAdam Hornacek import java.security.InvalidParameterException; 41df712980SVladimir Kotal import java.text.ParseException; 42d8a7afe2SAdam Hornacek import java.util.ArrayList; 43d8a7afe2SAdam Hornacek import java.util.Arrays; 44d8a7afe2SAdam Hornacek import java.util.Collections; 45d8a7afe2SAdam Hornacek import java.util.Comparator; 468ff8eecbSVladimir Kotal import java.util.Date; 47d8a7afe2SAdam Hornacek import java.util.EnumSet; 48d8a7afe2SAdam Hornacek import java.util.List; 49d8a7afe2SAdam Hornacek import java.util.Objects; 50d8a7afe2SAdam Hornacek import java.util.Set; 51d8a7afe2SAdam Hornacek import java.util.SortedSet; 52d8a7afe2SAdam Hornacek import java.util.TreeSet; 53d8a7afe2SAdam Hornacek import java.util.concurrent.ExecutorService; 54d8a7afe2SAdam Hornacek import java.util.concurrent.Future; 55d8a7afe2SAdam Hornacek import java.util.logging.Level; 56d8a7afe2SAdam Hornacek import java.util.logging.Logger; 57d8a7afe2SAdam Hornacek import java.util.regex.Pattern; 58d8a7afe2SAdam Hornacek import java.util.stream.Collectors; 59d8a7afe2SAdam Hornacek 60aa6abf42SAdam Hornacek import jakarta.servlet.ServletRequest; 61aa6abf42SAdam Hornacek import jakarta.servlet.http.Cookie; 62aa6abf42SAdam Hornacek import jakarta.servlet.http.HttpServletRequest; 63aa6abf42SAdam Hornacek import jakarta.servlet.http.HttpServletResponse; 64aa6abf42SAdam Hornacek import jakarta.ws.rs.core.HttpHeaders; 658ff8eecbSVladimir Kotal import org.apache.lucene.document.DateTools; 6636b6a6f8SVladimir Kotal import org.apache.lucene.document.Document; 6736b6a6f8SVladimir Kotal import org.jetbrains.annotations.Nullable; 6834c930beSVladimir Kotal import org.jetbrains.annotations.VisibleForTesting; 69d8a7afe2SAdam Hornacek import org.opengrok.indexer.Info; 70d8a7afe2SAdam Hornacek import org.opengrok.indexer.analysis.AbstractAnalyzer; 71d8a7afe2SAdam Hornacek import org.opengrok.indexer.analysis.AnalyzerGuru; 72d8a7afe2SAdam Hornacek import org.opengrok.indexer.analysis.ExpandTabsReader; 73d8a7afe2SAdam Hornacek import org.opengrok.indexer.analysis.StreamSource; 74d8a7afe2SAdam Hornacek import org.opengrok.indexer.authorization.AuthorizationFramework; 75d8a7afe2SAdam Hornacek import org.opengrok.indexer.configuration.Group; 76d8a7afe2SAdam Hornacek import org.opengrok.indexer.configuration.IgnoredNames; 77d8a7afe2SAdam Hornacek import org.opengrok.indexer.configuration.Project; 78d8a7afe2SAdam Hornacek import org.opengrok.indexer.configuration.RuntimeEnvironment; 79d8a7afe2SAdam Hornacek import org.opengrok.indexer.history.Annotation; 80d8a7afe2SAdam Hornacek import org.opengrok.indexer.history.HistoryEntry; 81d8a7afe2SAdam Hornacek import org.opengrok.indexer.history.HistoryException; 82d8a7afe2SAdam Hornacek import org.opengrok.indexer.history.HistoryGuru; 8336b6a6f8SVladimir Kotal import org.opengrok.indexer.index.IndexDatabase; 84d8a7afe2SAdam Hornacek import org.opengrok.indexer.logger.LoggerFactory; 85d8a7afe2SAdam Hornacek import org.opengrok.indexer.search.QueryBuilder; 86d8a7afe2SAdam Hornacek import org.opengrok.indexer.util.IOUtils; 87d8a7afe2SAdam Hornacek import org.opengrok.indexer.util.LineBreaker; 88d8a7afe2SAdam Hornacek import org.opengrok.indexer.util.TandemPath; 89d8a7afe2SAdam Hornacek import org.opengrok.indexer.web.EftarFileReader; 90d8a7afe2SAdam Hornacek import org.opengrok.indexer.web.Laundromat; 91d8a7afe2SAdam Hornacek import org.opengrok.indexer.web.Prefix; 92d8a7afe2SAdam Hornacek import org.opengrok.indexer.web.QueryParameters; 93d8a7afe2SAdam Hornacek import org.opengrok.indexer.web.SearchHelper; 94d8a7afe2SAdam Hornacek import org.opengrok.indexer.web.SortOrder; 95d8a7afe2SAdam Hornacek import org.opengrok.indexer.web.Util; 96d8a7afe2SAdam Hornacek import org.opengrok.indexer.web.messages.MessagesContainer.AcceptedMessage; 97d8a7afe2SAdam Hornacek import org.suigeneris.jrcs.diff.Diff; 98d8a7afe2SAdam Hornacek import org.suigeneris.jrcs.diff.DifferentiationFailedException; 99d8a7afe2SAdam Hornacek 100d8a7afe2SAdam Hornacek /** 101d8a7afe2SAdam Hornacek * A simple container to lazy initialize common vars wrt. a single request. It 102d8a7afe2SAdam Hornacek * MUST NOT be shared between several requests and 103d8a7afe2SAdam Hornacek * {@link #cleanup(ServletRequest)} should be called before the page context 104d8a7afe2SAdam Hornacek * gets destroyed (e.g.when leaving the {@code service} method). 105d8a7afe2SAdam Hornacek * <p> 106d8a7afe2SAdam Hornacek * Purpose is to decouple implementation details from web design, so that the 107d8a7afe2SAdam Hornacek * JSP developer does not need to know every implementation detail and normally 108d8a7afe2SAdam Hornacek * has to deal with this class/wrapper, only (so some people may like to call 109379f387bSVladimir Kotal * this class a bean with request scope ;-)). Furthermore, it helps to keep the 110d8a7afe2SAdam Hornacek * pages (how content gets generated) consistent and to document the request 111d8a7afe2SAdam Hornacek * parameters used. 112d8a7afe2SAdam Hornacek * <p> 113d8a7afe2SAdam Hornacek * General contract for this class (i.e. if not explicitly documented): no 114d8a7afe2SAdam Hornacek * method of this class changes neither the request nor the response. 115d8a7afe2SAdam Hornacek * 116d8a7afe2SAdam Hornacek * @author Jens Elkner 117d8a7afe2SAdam Hornacek */ 118d8a7afe2SAdam Hornacek public final class PageConfig { 119d8a7afe2SAdam Hornacek 120d8a7afe2SAdam Hornacek private static final Logger LOGGER = LoggerFactory.getLogger(PageConfig.class); 121d8a7afe2SAdam Hornacek 122d8a7afe2SAdam Hornacek // cookie name 123d8a7afe2SAdam Hornacek public static final String OPEN_GROK_PROJECT = "OpenGrokProject"; 124d8a7afe2SAdam Hornacek 1257c7653c2SVladimir Kotal public static final String DUMMY_REVISION = "unknown"; 1267c7653c2SVladimir Kotal 127d8a7afe2SAdam Hornacek // query parameters 128379f387bSVladimir Kotal static final String PROJECT_PARAM_NAME = "project"; 129379f387bSVladimir Kotal static final String GROUP_PARAM_NAME = "group"; 130d8a7afe2SAdam Hornacek private static final String DEBUG_PARAM_NAME = "debug"; 131d8a7afe2SAdam Hornacek 132d8a7afe2SAdam Hornacek // TODO if still used, get it from the app context 133d8a7afe2SAdam Hornacek 134d8a7afe2SAdam Hornacek private final AuthorizationFramework authFramework; 135d8a7afe2SAdam Hornacek private RuntimeEnvironment env; 136d8a7afe2SAdam Hornacek private IgnoredNames ignoredNames; 137d8a7afe2SAdam Hornacek private String path; 138d8a7afe2SAdam Hornacek private File resourceFile; 139d8a7afe2SAdam Hornacek private String resourcePath; 140d8a7afe2SAdam Hornacek private EftarFileReader eftarReader; 141d8a7afe2SAdam Hornacek private String sourceRootPath; 142d8a7afe2SAdam Hornacek private Boolean isDir; 143d8a7afe2SAdam Hornacek private String uriEncodedPath; 144d8a7afe2SAdam Hornacek private Prefix prefix; 145d8a7afe2SAdam Hornacek private String pageTitle; 146d8a7afe2SAdam Hornacek private String dtag; 147d8a7afe2SAdam Hornacek private String rev; 148d8a7afe2SAdam Hornacek private String fragmentIdentifier; // Also settable via match offset translation 149d8a7afe2SAdam Hornacek private Boolean hasAnnotation; 150d8a7afe2SAdam Hornacek private Boolean annotate; 151d8a7afe2SAdam Hornacek private Annotation annotation; 152d8a7afe2SAdam Hornacek private Boolean hasHistory; 153d8a7afe2SAdam Hornacek private static final EnumSet<AbstractAnalyzer.Genre> txtGenres 154d8a7afe2SAdam Hornacek = EnumSet.of(AbstractAnalyzer.Genre.DATA, AbstractAnalyzer.Genre.PLAIN, AbstractAnalyzer.Genre.HTML); 155d8a7afe2SAdam Hornacek private SortedSet<String> requestedProjects; 156d8a7afe2SAdam Hornacek private String requestedProjectsString; 157d8a7afe2SAdam Hornacek private List<String> dirFileList; 158d8a7afe2SAdam Hornacek private QueryBuilder queryBuilder; 159d8a7afe2SAdam Hornacek private File dataRoot; 160d8a7afe2SAdam Hornacek private StringBuilder headLines; 161d8a7afe2SAdam Hornacek /** 162d8a7afe2SAdam Hornacek * Page java scripts. 163d8a7afe2SAdam Hornacek */ 164d8a7afe2SAdam Hornacek private final Scripts scripts = new Scripts(); 165d8a7afe2SAdam Hornacek 166d8a7afe2SAdam Hornacek private static final String ATTR_NAME = PageConfig.class.getCanonicalName(); 167d8a7afe2SAdam Hornacek private HttpServletRequest req; 168d8a7afe2SAdam Hornacek 169c6f0939bSAdam Hornacek private final ExecutorService executor; 170d8a7afe2SAdam Hornacek 171d8a7afe2SAdam Hornacek /** 172d8a7afe2SAdam Hornacek * Sets current request's attribute. 173d8a7afe2SAdam Hornacek * 174d8a7afe2SAdam Hornacek * @param attr attribute 175d8a7afe2SAdam Hornacek * @param val value 176d8a7afe2SAdam Hornacek */ setRequestAttribute(String attr, Object val)177d8a7afe2SAdam Hornacek public void setRequestAttribute(String attr, Object val) { 178d8a7afe2SAdam Hornacek this.req.setAttribute(attr, val); 179d8a7afe2SAdam Hornacek } 180d8a7afe2SAdam Hornacek 181d8a7afe2SAdam Hornacek /** 182d8a7afe2SAdam Hornacek * Gets current request's attribute. 183d8a7afe2SAdam Hornacek * @param attr attribute 184d8a7afe2SAdam Hornacek * @return Object attribute value or null if attribute does not exist 185d8a7afe2SAdam Hornacek */ getRequestAttribute(String attr)186d8a7afe2SAdam Hornacek public Object getRequestAttribute(String attr) { 187d8a7afe2SAdam Hornacek return this.req.getAttribute(attr); 188d8a7afe2SAdam Hornacek } 189d8a7afe2SAdam Hornacek 190d8a7afe2SAdam Hornacek /** 191d8a7afe2SAdam Hornacek * Removes an attribute from the current request. 192d8a7afe2SAdam Hornacek * @param string the attribute 193d8a7afe2SAdam Hornacek */ removeAttribute(String string)194d8a7afe2SAdam Hornacek public void removeAttribute(String string) { 195d8a7afe2SAdam Hornacek req.removeAttribute(string); 196d8a7afe2SAdam Hornacek } 197d8a7afe2SAdam Hornacek 198d8a7afe2SAdam Hornacek /** 199d8a7afe2SAdam Hornacek * Add the given data to the <head> section of the html page to 200d8a7afe2SAdam Hornacek * generate. 201d8a7afe2SAdam Hornacek * 202d8a7afe2SAdam Hornacek * @param data data to add. It is copied as is, so remember to escape 203d8a7afe2SAdam Hornacek * special characters ... 204d8a7afe2SAdam Hornacek */ addHeaderData(String data)205d8a7afe2SAdam Hornacek public void addHeaderData(String data) { 206d8a7afe2SAdam Hornacek if (data == null || data.length() == 0) { 207d8a7afe2SAdam Hornacek return; 208d8a7afe2SAdam Hornacek } 209d8a7afe2SAdam Hornacek if (headLines == null) { 210d8a7afe2SAdam Hornacek headLines = new StringBuilder(); 211d8a7afe2SAdam Hornacek } 212d8a7afe2SAdam Hornacek headLines.append(data); 213d8a7afe2SAdam Hornacek } 214d8a7afe2SAdam Hornacek 215d8a7afe2SAdam Hornacek /** 216d8a7afe2SAdam Hornacek * Get addition data, which should be added as is to the <head> 217d8a7afe2SAdam Hornacek * section of the html page. 218d8a7afe2SAdam Hornacek * 219d8a7afe2SAdam Hornacek * @return an empty string if nothing to add, the data otherwise. 220d8a7afe2SAdam Hornacek */ getHeaderData()221d8a7afe2SAdam Hornacek public String getHeaderData() { 222d8a7afe2SAdam Hornacek return headLines == null ? "" : headLines.toString(); 223d8a7afe2SAdam Hornacek } 224d8a7afe2SAdam Hornacek 225d8a7afe2SAdam Hornacek /** 226379f387bSVladimir Kotal * Extract file path and revision strings from the URL. 227379f387bSVladimir Kotal * @param data DiffData object 228379f387bSVladimir Kotal * @param context context path 229379f387bSVladimir Kotal * @param filepath file path array (output parameter) 230379f387bSVladimir Kotal * @return true if the extraction was successful, false otherwise 231379f387bSVladimir Kotal * (in which case {@link DiffData#errorMsg} will be set) 232d8a7afe2SAdam Hornacek */ getFileRevision(DiffData data, String context, String[] filepath)233379f387bSVladimir Kotal private boolean getFileRevision(DiffData data, String context, String[] filepath) { 234d8a7afe2SAdam Hornacek /* 235d8a7afe2SAdam Hornacek * Basically the request URI looks like this: 236d8a7afe2SAdam Hornacek * http://$site/$webapp/diff/$resourceFile?r1=$fileA@$revA&r2=$fileB@$revB 237d8a7afe2SAdam Hornacek * The code below extracts file path and revision from the URI. 238d8a7afe2SAdam Hornacek */ 239d8a7afe2SAdam Hornacek for (int i = 1; i <= 2; i++) { 240d8a7afe2SAdam Hornacek String p = req.getParameter(QueryParameters.REVISION_PARAM + i); 241d8a7afe2SAdam Hornacek if (p != null) { 242d8a7afe2SAdam Hornacek int j = p.lastIndexOf("@"); 243d8a7afe2SAdam Hornacek if (j != -1) { 244d8a7afe2SAdam Hornacek filepath[i - 1] = p.substring(0, j); 245d8a7afe2SAdam Hornacek data.rev[i - 1] = p.substring(j + 1); 246d8a7afe2SAdam Hornacek } 247d8a7afe2SAdam Hornacek } 248d8a7afe2SAdam Hornacek } 249379f387bSVladimir Kotal 250d8a7afe2SAdam Hornacek if (data.rev[0] == null || data.rev[1] == null 251d8a7afe2SAdam Hornacek || data.rev[0].length() == 0 || data.rev[1].length() == 0 252d8a7afe2SAdam Hornacek || data.rev[0].equals(data.rev[1])) { 253d8a7afe2SAdam Hornacek data.errorMsg = "Please pick two revisions to compare the changed " 254d8a7afe2SAdam Hornacek + "from the <a href=\"" + context + Prefix.HIST_L 255d8a7afe2SAdam Hornacek + getUriEncodedPath() + "\">history</a>"; 256379f387bSVladimir Kotal return false; 257379f387bSVladimir Kotal } 258379f387bSVladimir Kotal 259379f387bSVladimir Kotal return true; 260379f387bSVladimir Kotal } 261379f387bSVladimir Kotal 262379f387bSVladimir Kotal /** 263379f387bSVladimir Kotal * Get all data required to create a diff view w.r.t. to this request in one go. 264379f387bSVladimir Kotal * 265379f387bSVladimir Kotal * @return an instance with just enough information to render a sufficient view. 266379f387bSVladimir Kotal * If not all required parameters were given either they are supplemented 267379f387bSVladimir Kotal * with reasonable defaults if possible, otherwise the related field(s) are {@code null}. 268379f387bSVladimir Kotal * {@link DiffData#errorMsg} 269379f387bSVladimir Kotal * {@code != null} indicates that an error occurred and one should not try to render a view. 270379f387bSVladimir Kotal */ getDiffData()271379f387bSVladimir Kotal public DiffData getDiffData() { 272379f387bSVladimir Kotal DiffData data = new DiffData(getPath().substring(0, getPath().lastIndexOf(PATH_SEPARATOR)), 273379f387bSVladimir Kotal Util.htmlize(getResourceFile().getName())); 274379f387bSVladimir Kotal 275379f387bSVladimir Kotal String srcRoot = getSourceRootPath(); 276379f387bSVladimir Kotal String context = req.getContextPath(); 277379f387bSVladimir Kotal String[] filepath = new String[2]; 278379f387bSVladimir Kotal 279379f387bSVladimir Kotal if (!getFileRevision(data, context, filepath)) { 280d8a7afe2SAdam Hornacek return data; 281d8a7afe2SAdam Hornacek } 282d8a7afe2SAdam Hornacek 283379f387bSVladimir Kotal data.genre = AnalyzerGuru.getGenre(getResourceFile().getName()); 284d8a7afe2SAdam Hornacek if (data.genre == null || txtGenres.contains(data.genre)) { 285d8a7afe2SAdam Hornacek InputStream[] in = new InputStream[2]; 286d8a7afe2SAdam Hornacek try { 287d8a7afe2SAdam Hornacek // Get input stream for both older and newer file. 288d8a7afe2SAdam Hornacek Future<?>[] future = new Future<?>[2]; 289d8a7afe2SAdam Hornacek for (int i = 0; i < 2; i++) { 290d8a7afe2SAdam Hornacek File f = new File(srcRoot + filepath[i]); 291d8a7afe2SAdam Hornacek final String revision = data.rev[i]; 292d8a7afe2SAdam Hornacek future[i] = executor.submit(() -> HistoryGuru.getInstance(). 293d8a7afe2SAdam Hornacek getRevision(f.getParent(), f.getName(), revision)); 294d8a7afe2SAdam Hornacek } 295d8a7afe2SAdam Hornacek 296d8a7afe2SAdam Hornacek for (int i = 0; i < 2; i++) { 297d8a7afe2SAdam Hornacek // The Executor used by given repository will enforce the timeout. 298d8a7afe2SAdam Hornacek in[i] = (InputStream) future[i].get(); 299d8a7afe2SAdam Hornacek if (in[i] == null) { 300d8a7afe2SAdam Hornacek data.errorMsg = "Unable to get revision " 301d8a7afe2SAdam Hornacek + Util.htmlize(data.rev[i]) + " for file: " 302d8a7afe2SAdam Hornacek + Util.htmlize(getPath()); 303d8a7afe2SAdam Hornacek return data; 304d8a7afe2SAdam Hornacek } 305d8a7afe2SAdam Hornacek } 306d8a7afe2SAdam Hornacek 307d8a7afe2SAdam Hornacek /* 308d8a7afe2SAdam Hornacek * If the genre of the older revision cannot be determined, 309d8a7afe2SAdam Hornacek * (this can happen if the file was empty), try with newer 310d8a7afe2SAdam Hornacek * version. 311d8a7afe2SAdam Hornacek */ 312d8a7afe2SAdam Hornacek for (int i = 0; i < 2 && data.genre == null; i++) { 313d8a7afe2SAdam Hornacek try { 314d8a7afe2SAdam Hornacek data.genre = AnalyzerGuru.getGenre(in[i]); 315d8a7afe2SAdam Hornacek } catch (IOException e) { 316d8a7afe2SAdam Hornacek data.errorMsg = "Unable to determine the file type: " 317d8a7afe2SAdam Hornacek + Util.htmlize(e.getMessage()); 318d8a7afe2SAdam Hornacek } 319d8a7afe2SAdam Hornacek } 320d8a7afe2SAdam Hornacek 321d8a7afe2SAdam Hornacek if (data.genre != AbstractAnalyzer.Genre.PLAIN && data.genre != AbstractAnalyzer.Genre.HTML) { 322d8a7afe2SAdam Hornacek return data; 323d8a7afe2SAdam Hornacek } 324d8a7afe2SAdam Hornacek 325d8a7afe2SAdam Hornacek ArrayList<String> lines = new ArrayList<>(); 326d8a7afe2SAdam Hornacek Project p = getProject(); 327d8a7afe2SAdam Hornacek for (int i = 0; i < 2; i++) { 328d8a7afe2SAdam Hornacek // All files under source root are read with UTF-8 as a default. 329d8a7afe2SAdam Hornacek try (BufferedReader br = new BufferedReader( 330d8a7afe2SAdam Hornacek ExpandTabsReader.wrap(IOUtils.createBOMStrippedReader( 331d8a7afe2SAdam Hornacek in[i], StandardCharsets.UTF_8.name()), p))) { 332d8a7afe2SAdam Hornacek String line; 333d8a7afe2SAdam Hornacek while ((line = br.readLine()) != null) { 334d8a7afe2SAdam Hornacek lines.add(line); 335d8a7afe2SAdam Hornacek } 336d8a7afe2SAdam Hornacek data.file[i] = lines.toArray(new String[0]); 337d8a7afe2SAdam Hornacek lines.clear(); 338d8a7afe2SAdam Hornacek } 339d8a7afe2SAdam Hornacek in[i] = null; 340d8a7afe2SAdam Hornacek } 341d8a7afe2SAdam Hornacek } catch (Exception e) { 342d8a7afe2SAdam Hornacek data.errorMsg = "Error reading revisions: " 343d8a7afe2SAdam Hornacek + Util.htmlize(e.getMessage()); 344d8a7afe2SAdam Hornacek } finally { 345d8a7afe2SAdam Hornacek for (int i = 0; i < 2; i++) { 346d8a7afe2SAdam Hornacek IOUtils.close(in[i]); 347d8a7afe2SAdam Hornacek } 348d8a7afe2SAdam Hornacek } 349d8a7afe2SAdam Hornacek if (data.errorMsg != null) { 350d8a7afe2SAdam Hornacek return data; 351d8a7afe2SAdam Hornacek } 352d8a7afe2SAdam Hornacek try { 353d8a7afe2SAdam Hornacek data.revision = Diff.diff(data.file[0], data.file[1]); 354d8a7afe2SAdam Hornacek } catch (DifferentiationFailedException e) { 355379f387bSVladimir Kotal data.errorMsg = "Unable to get diffs: " + Util.htmlize(e.getMessage()); 356d8a7afe2SAdam Hornacek } 357d8a7afe2SAdam Hornacek for (int i = 0; i < 2; i++) { 358d8a7afe2SAdam Hornacek try { 359d8a7afe2SAdam Hornacek URI u = new URI(null, null, null, 360d8a7afe2SAdam Hornacek filepath[i] + "@" + data.rev[i], null); 361d8a7afe2SAdam Hornacek data.param[i] = u.getRawQuery(); 362d8a7afe2SAdam Hornacek } catch (URISyntaxException e) { 363d8a7afe2SAdam Hornacek LOGGER.log(Level.WARNING, "Failed to create URI: ", e); 364d8a7afe2SAdam Hornacek } 365d8a7afe2SAdam Hornacek } 366d8a7afe2SAdam Hornacek data.full = fullDiff(); 367d8a7afe2SAdam Hornacek data.type = getDiffType(); 368d8a7afe2SAdam Hornacek } 369d8a7afe2SAdam Hornacek return data; 370d8a7afe2SAdam Hornacek } 371d8a7afe2SAdam Hornacek 372d8a7afe2SAdam Hornacek /** 373d8a7afe2SAdam Hornacek * Get the diff display type to use wrt. the request parameter 374d8a7afe2SAdam Hornacek * {@code format}. 375d8a7afe2SAdam Hornacek * 376d8a7afe2SAdam Hornacek * @return {@link DiffType#SIDEBYSIDE} if the request contains no such 377d8a7afe2SAdam Hornacek * parameter or one with an unknown value, the recognized diff type 378d8a7afe2SAdam Hornacek * otherwise. 379d8a7afe2SAdam Hornacek * @see DiffType#get(String) 380d8a7afe2SAdam Hornacek * @see DiffType#getAbbrev() 381d8a7afe2SAdam Hornacek * @see DiffType#toString() 382d8a7afe2SAdam Hornacek */ getDiffType()383d8a7afe2SAdam Hornacek public DiffType getDiffType() { 384d8a7afe2SAdam Hornacek DiffType d = DiffType.get(req.getParameter(QueryParameters.FORMAT_PARAM)); 385d8a7afe2SAdam Hornacek return d == null ? DiffType.SIDEBYSIDE : d; 386d8a7afe2SAdam Hornacek } 387d8a7afe2SAdam Hornacek 388d8a7afe2SAdam Hornacek /** 389d8a7afe2SAdam Hornacek * Check, whether a full diff should be displayed. 390d8a7afe2SAdam Hornacek * 391d8a7afe2SAdam Hornacek * @return {@code true} if a request parameter {@code full} with the literal 392d8a7afe2SAdam Hornacek * value {@code 1} was found. 393d8a7afe2SAdam Hornacek */ fullDiff()394d8a7afe2SAdam Hornacek public boolean fullDiff() { 395d8a7afe2SAdam Hornacek String val = req.getParameter(QueryParameters.DIFF_LEVEL_PARAM); 396d8a7afe2SAdam Hornacek return val != null && val.equals("1"); 397d8a7afe2SAdam Hornacek } 398d8a7afe2SAdam Hornacek 399d8a7afe2SAdam Hornacek /** 400d8a7afe2SAdam Hornacek * Check, whether the request contains minimal information required to 401d8a7afe2SAdam Hornacek * produce a valid page. If this method returns an empty string, the 402d8a7afe2SAdam Hornacek * referred file or directory actually exists below the source root 403d8a7afe2SAdam Hornacek * directory and is readable. 404d8a7afe2SAdam Hornacek * 405d8a7afe2SAdam Hornacek * @return {@code null} if the referred src file, directory or history is 406d8a7afe2SAdam Hornacek * not available, an empty String if further processing is ok and a 407d8a7afe2SAdam Hornacek * non-empty string which contains the URI encoded redirect path if the 408d8a7afe2SAdam Hornacek * request should be redirected. 409d8a7afe2SAdam Hornacek * @see #resourceNotAvailable() 410d8a7afe2SAdam Hornacek * @see #getDirectoryRedirect() 411d8a7afe2SAdam Hornacek */ canProcess()412d8a7afe2SAdam Hornacek public String canProcess() { 413d8a7afe2SAdam Hornacek if (resourceNotAvailable()) { 414d8a7afe2SAdam Hornacek return null; 415d8a7afe2SAdam Hornacek } 416d8a7afe2SAdam Hornacek String redir = getDirectoryRedirect(); 417d8a7afe2SAdam Hornacek if (redir == null && getPrefix() == Prefix.HIST_L && !hasHistory()) { 418d8a7afe2SAdam Hornacek return null; 419d8a7afe2SAdam Hornacek } 420d8a7afe2SAdam Hornacek // jel: outfactored from list.jsp - seems to be bogus 421d8a7afe2SAdam Hornacek if (isDir()) { 422d8a7afe2SAdam Hornacek if (getPrefix() == Prefix.XREF_P) { 423d8a7afe2SAdam Hornacek if (getResourceFileList().isEmpty() 424d8a7afe2SAdam Hornacek && !getRequestedRevision().isEmpty() && !hasHistory()) { 425d8a7afe2SAdam Hornacek return null; 426d8a7afe2SAdam Hornacek } 427d8a7afe2SAdam Hornacek } else if ((getPrefix() == Prefix.RAW_P) 428d8a7afe2SAdam Hornacek || (getPrefix() == Prefix.DOWNLOAD_P)) { 429d8a7afe2SAdam Hornacek return null; 430d8a7afe2SAdam Hornacek } 431d8a7afe2SAdam Hornacek } 432d8a7afe2SAdam Hornacek return redir == null ? "" : redir; 433d8a7afe2SAdam Hornacek } 434d8a7afe2SAdam Hornacek 435d8a7afe2SAdam Hornacek /** 436d8a7afe2SAdam Hornacek * Get a list of filenames in the requested path. 437d8a7afe2SAdam Hornacek * 438d8a7afe2SAdam Hornacek * @return an empty list, if the resource does not exist, is not a directory 439d8a7afe2SAdam Hornacek * or an error occurred when reading it, otherwise a list of filenames in 440d8a7afe2SAdam Hornacek * that directory, sorted alphabetically 441d8a7afe2SAdam Hornacek * 442d8a7afe2SAdam Hornacek * <p> 443d8a7afe2SAdam Hornacek * For the root directory (/xref/) an authorization is performed for each 4444a04c503SChris Fraire * project in case that projects are used. 445d8a7afe2SAdam Hornacek * 446d8a7afe2SAdam Hornacek * @see #getResourceFile() 447d8a7afe2SAdam Hornacek * @see #isDir() 448d8a7afe2SAdam Hornacek */ getResourceFileList()449d8a7afe2SAdam Hornacek public List<String> getResourceFileList() { 450d8a7afe2SAdam Hornacek if (dirFileList == null) { 451d8a7afe2SAdam Hornacek File[] files = null; 452d8a7afe2SAdam Hornacek if (isDir() && getResourcePath().length() > 1) { 453d8a7afe2SAdam Hornacek files = getResourceFile().listFiles(); 454d8a7afe2SAdam Hornacek } 45534c930beSVladimir Kotal 456d8a7afe2SAdam Hornacek if (files == null) { 457d8a7afe2SAdam Hornacek dirFileList = Collections.emptyList(); 458d8a7afe2SAdam Hornacek } else { 45934c930beSVladimir Kotal List<String> listOfFiles = getSortedFiles(files); 460d8a7afe2SAdam Hornacek 461d8a7afe2SAdam Hornacek if (env.hasProjects() && getPath().isEmpty()) { 462379f387bSVladimir Kotal /* 463d8a7afe2SAdam Hornacek * This denotes the source root directory, we need to filter 464d8a7afe2SAdam Hornacek * projects which aren't allowed by the authorization 465d8a7afe2SAdam Hornacek * because otherwise the main xref page expose the names of 466d8a7afe2SAdam Hornacek * all projects in OpenGrok even those which aren't allowed 467d8a7afe2SAdam Hornacek * for the particular user. E.g. remove all which aren't 468d8a7afe2SAdam Hornacek * among the filtered set of projects. 469d8a7afe2SAdam Hornacek * 470d8a7afe2SAdam Hornacek * The authorization check is made in 471d8a7afe2SAdam Hornacek * {@link ProjectHelper#getAllProjects()} as a part of all 472d8a7afe2SAdam Hornacek * projects filtering. 473d8a7afe2SAdam Hornacek */ 474d8a7afe2SAdam Hornacek List<String> modifiableListOfFiles = new ArrayList<>(listOfFiles); 475c6f0939bSAdam Hornacek modifiableListOfFiles.removeIf(t -> getProjectHelper().getAllProjects().stream() 476c6f0939bSAdam Hornacek .noneMatch(p -> p.getName().equalsIgnoreCase(t))); 477d8a7afe2SAdam Hornacek return dirFileList = Collections.unmodifiableList(modifiableListOfFiles); 478d8a7afe2SAdam Hornacek } 479d8a7afe2SAdam Hornacek 480d8a7afe2SAdam Hornacek dirFileList = Collections.unmodifiableList(listOfFiles); 481d8a7afe2SAdam Hornacek } 482d8a7afe2SAdam Hornacek } 483d8a7afe2SAdam Hornacek return dirFileList; 484d8a7afe2SAdam Hornacek } 485d8a7afe2SAdam Hornacek getFileComparator()48634c930beSVladimir Kotal private Comparator<File> getFileComparator() { 48734c930beSVladimir Kotal if (getEnv().getListDirsFirst()) { 48834c930beSVladimir Kotal return (f1, f2) -> { 48934c930beSVladimir Kotal if (f1.isDirectory() && !f2.isDirectory()) { 49034c930beSVladimir Kotal return -1; 49134c930beSVladimir Kotal } else if (!f1.isDirectory() && f2.isDirectory()) { 49234c930beSVladimir Kotal return 1; 49334c930beSVladimir Kotal } else { 49434c930beSVladimir Kotal return f1.getName().compareTo(f2.getName()); 49534c930beSVladimir Kotal } 49634c930beSVladimir Kotal }; 49734c930beSVladimir Kotal } else { 49834c930beSVladimir Kotal return Comparator.comparing(File::getName); 49934c930beSVladimir Kotal } 50034c930beSVladimir Kotal } 50134c930beSVladimir Kotal 50234c930beSVladimir Kotal @VisibleForTesting 50334c930beSVladimir Kotal List<String> getSortedFiles(File[] files) { 50434c930beSVladimir Kotal return Arrays.stream(files).sorted(getFileComparator()).map(File::getName).collect(Collectors.toList()); 50534c930beSVladimir Kotal } 50634c930beSVladimir Kotal 507d8a7afe2SAdam Hornacek /** 508d8a7afe2SAdam Hornacek * Get the time of last modification of the related file or directory. 509d8a7afe2SAdam Hornacek * 510d8a7afe2SAdam Hornacek * @return the last modification time of the related file or directory. 511d8a7afe2SAdam Hornacek * @see File#lastModified() 512d8a7afe2SAdam Hornacek */ 513d8a7afe2SAdam Hornacek public long getLastModified() { 514d8a7afe2SAdam Hornacek return getResourceFile().lastModified(); 515d8a7afe2SAdam Hornacek } 516d8a7afe2SAdam Hornacek 517d8a7afe2SAdam Hornacek /** 518d8a7afe2SAdam Hornacek * Get all RSS related directories from the request using its {@code also} 519d8a7afe2SAdam Hornacek * parameter. 520d8a7afe2SAdam Hornacek * 521d8a7afe2SAdam Hornacek * @return an empty string if the requested resource is not a directory, a 522d8a7afe2SAdam Hornacek * space (' ') separated list of unchecked directory names otherwise. 523d8a7afe2SAdam Hornacek */ 524d8a7afe2SAdam Hornacek public String getHistoryDirs() { 525d8a7afe2SAdam Hornacek if (!isDir()) { 526d8a7afe2SAdam Hornacek return ""; 527d8a7afe2SAdam Hornacek } 528d8a7afe2SAdam Hornacek String[] val = req.getParameterValues("also"); 529d8a7afe2SAdam Hornacek if (val == null || val.length == 0) { 530d8a7afe2SAdam Hornacek return getPath(); 531d8a7afe2SAdam Hornacek } 532d8a7afe2SAdam Hornacek StringBuilder paths = new StringBuilder(getPath()); 533d8a7afe2SAdam Hornacek for (String val1 : val) { 534d8a7afe2SAdam Hornacek paths.append(' ').append(val1); 535d8a7afe2SAdam Hornacek } 536d8a7afe2SAdam Hornacek return paths.toString(); 537d8a7afe2SAdam Hornacek } 538d8a7afe2SAdam Hornacek 539d8a7afe2SAdam Hornacek /** 540d8a7afe2SAdam Hornacek * Get the int value of the given request parameter. 541d8a7afe2SAdam Hornacek * 542d8a7afe2SAdam Hornacek * @param name name of the parameter to lookup. 543d8a7afe2SAdam Hornacek * @param defaultValue value to return, if the parameter is not set, is not 544d8a7afe2SAdam Hornacek * a number, or is < 0. 545d8a7afe2SAdam Hornacek * @return the parsed int value on success, the given default value 546d8a7afe2SAdam Hornacek * otherwise. 547d8a7afe2SAdam Hornacek */ 548d8a7afe2SAdam Hornacek public int getIntParam(String name, int defaultValue) { 549d8a7afe2SAdam Hornacek int ret = defaultValue; 550d8a7afe2SAdam Hornacek String s = req.getParameter(name); 551d8a7afe2SAdam Hornacek if (s != null && s.length() != 0) { 552d8a7afe2SAdam Hornacek try { 553d8a7afe2SAdam Hornacek int x = Integer.parseInt(s, 10); 554d8a7afe2SAdam Hornacek if (x >= 0) { 555d8a7afe2SAdam Hornacek ret = x; 556d8a7afe2SAdam Hornacek } 557d8a7afe2SAdam Hornacek } catch (NumberFormatException e) { 558379f387bSVladimir Kotal LOGGER.log(Level.INFO, String.format("Failed to parse %s integer %s", name, s), e); 559d8a7afe2SAdam Hornacek } 560d8a7afe2SAdam Hornacek } 561d8a7afe2SAdam Hornacek return ret; 562d8a7afe2SAdam Hornacek } 563d8a7afe2SAdam Hornacek 564d8a7afe2SAdam Hornacek /** 565d8a7afe2SAdam Hornacek * Get the <b>start</b> index for a search result to return by looking up 566d8a7afe2SAdam Hornacek * the {@code start} request parameter. 567d8a7afe2SAdam Hornacek * 568d8a7afe2SAdam Hornacek * @return 0 if the corresponding start parameter is not set or not a 569d8a7afe2SAdam Hornacek * number, the number found otherwise. 570d8a7afe2SAdam Hornacek */ 571d8a7afe2SAdam Hornacek public int getSearchStart() { 572d8a7afe2SAdam Hornacek return getIntParam(QueryParameters.START_PARAM, 0); 573d8a7afe2SAdam Hornacek } 574d8a7afe2SAdam Hornacek 575d8a7afe2SAdam Hornacek /** 576d8a7afe2SAdam Hornacek * Get the number of search results to max. return by looking up the 577d8a7afe2SAdam Hornacek * {@code n} request parameter. 578d8a7afe2SAdam Hornacek * 579d8a7afe2SAdam Hornacek * @return the default number of hits if the corresponding start parameter 580d8a7afe2SAdam Hornacek * is not set or not a number, the number found otherwise. 581d8a7afe2SAdam Hornacek */ 582d8a7afe2SAdam Hornacek public int getSearchMaxItems() { 583d8a7afe2SAdam Hornacek return getIntParam(QueryParameters.COUNT_PARAM, getEnv().getHitsPerPage()); 584d8a7afe2SAdam Hornacek } 585d8a7afe2SAdam Hornacek 586d8a7afe2SAdam Hornacek public int getRevisionMessageCollapseThreshold() { 587d8a7afe2SAdam Hornacek return getEnv().getRevisionMessageCollapseThreshold(); 588d8a7afe2SAdam Hornacek } 589d8a7afe2SAdam Hornacek 590d8a7afe2SAdam Hornacek public int getCurrentIndexedCollapseThreshold() { 591d8a7afe2SAdam Hornacek return getEnv().getCurrentIndexedCollapseThreshold(); 592d8a7afe2SAdam Hornacek } 593d8a7afe2SAdam Hornacek 594d8a7afe2SAdam Hornacek public int getGroupsCollapseThreshold() { 595d8a7afe2SAdam Hornacek return getEnv().getGroupsCollapseThreshold(); 596d8a7afe2SAdam Hornacek } 597d8a7afe2SAdam Hornacek 598d8a7afe2SAdam Hornacek /** 599d8a7afe2SAdam Hornacek * Get sort orders from the request parameter {@code sort} and if this list 600379f387bSVladimir Kotal * would be empty from the cookie {@code OpenGrokSorting}. 601d8a7afe2SAdam Hornacek * 602d8a7afe2SAdam Hornacek * @return a possible empty list which contains the sort order values in the 603d8a7afe2SAdam Hornacek * same order supplied by the request parameter or cookie(s). 604d8a7afe2SAdam Hornacek */ 605d8a7afe2SAdam Hornacek public List<SortOrder> getSortOrder() { 606d8a7afe2SAdam Hornacek List<SortOrder> sort = new ArrayList<>(); 607379f387bSVladimir Kotal List<String> vals = getParameterValues(QueryParameters.SORT_PARAM); 608d8a7afe2SAdam Hornacek for (String s : vals) { 609d8a7afe2SAdam Hornacek SortOrder so = SortOrder.get(s); 610d8a7afe2SAdam Hornacek if (so != null) { 611d8a7afe2SAdam Hornacek sort.add(so); 612d8a7afe2SAdam Hornacek } 613d8a7afe2SAdam Hornacek } 614d8a7afe2SAdam Hornacek if (sort.isEmpty()) { 615d8a7afe2SAdam Hornacek vals = getCookieVals("OpenGrokSorting"); 616d8a7afe2SAdam Hornacek for (String s : vals) { 617d8a7afe2SAdam Hornacek SortOrder so = SortOrder.get(s); 618d8a7afe2SAdam Hornacek if (so != null) { 619d8a7afe2SAdam Hornacek sort.add(so); 620d8a7afe2SAdam Hornacek } 621d8a7afe2SAdam Hornacek } 622d8a7afe2SAdam Hornacek } 623d8a7afe2SAdam Hornacek return sort; 624d8a7afe2SAdam Hornacek } 625d8a7afe2SAdam Hornacek 626d8a7afe2SAdam Hornacek /** 627d8a7afe2SAdam Hornacek * Get a reference to the {@code QueryBuilder} wrt. to the current request 628d8a7afe2SAdam Hornacek * parameters: <dl> <dt>q</dt> <dd>freetext lookup rules</dd> <dt>defs</dt> 629d8a7afe2SAdam Hornacek * <dd>definitions lookup rules</dd> <dt>path</dt> <dd>path related 630d8a7afe2SAdam Hornacek * rules</dd> <dt>hist</dt> <dd>history related rules</dd> </dl> 631d8a7afe2SAdam Hornacek * 632d8a7afe2SAdam Hornacek * @return a query builder with all relevant fields populated. 633d8a7afe2SAdam Hornacek */ 634d8a7afe2SAdam Hornacek public QueryBuilder getQueryBuilder() { 635d8a7afe2SAdam Hornacek if (queryBuilder == null) { 636d8a7afe2SAdam Hornacek queryBuilder = new QueryBuilder(). 637d8a7afe2SAdam Hornacek setFreetext(Laundromat.launderQuery(req.getParameter(QueryBuilder.FULL))) 638d8a7afe2SAdam Hornacek .setDefs(Laundromat.launderQuery(req.getParameter(QueryBuilder.DEFS))) 639d8a7afe2SAdam Hornacek .setRefs(Laundromat.launderQuery(req.getParameter(QueryBuilder.REFS))) 640d8a7afe2SAdam Hornacek .setPath(Laundromat.launderQuery(req.getParameter(QueryBuilder.PATH))) 641d8a7afe2SAdam Hornacek .setHist(Laundromat.launderQuery(req.getParameter(QueryBuilder.HIST))) 642d8a7afe2SAdam Hornacek .setType(Laundromat.launderQuery(req.getParameter(QueryBuilder.TYPE))); 643d8a7afe2SAdam Hornacek } 644d8a7afe2SAdam Hornacek 645d8a7afe2SAdam Hornacek return queryBuilder; 646d8a7afe2SAdam Hornacek } 647d8a7afe2SAdam Hornacek 648d8a7afe2SAdam Hornacek /** 649379f387bSVladimir Kotal * Get the <i>Eftar</i> reader for the data directory. If it has been already 650d8a7afe2SAdam Hornacek * opened and not closed, this instance gets returned. One should not close 651d8a7afe2SAdam Hornacek * it once used: {@link #cleanup(ServletRequest)} takes care to close it. 652d8a7afe2SAdam Hornacek * 653d8a7afe2SAdam Hornacek * @return {@code null} if a reader can't be established, the reader 654d8a7afe2SAdam Hornacek * otherwise. 655d8a7afe2SAdam Hornacek */ 656d8a7afe2SAdam Hornacek public EftarFileReader getEftarReader() { 657d8a7afe2SAdam Hornacek if (eftarReader == null || eftarReader.isClosed()) { 658d8a7afe2SAdam Hornacek File f = getEnv().getDtagsEftar(); 659d8a7afe2SAdam Hornacek if (f == null) { 660d8a7afe2SAdam Hornacek eftarReader = null; 661d8a7afe2SAdam Hornacek } else { 662d8a7afe2SAdam Hornacek try { 663d8a7afe2SAdam Hornacek eftarReader = new EftarFileReader(f); 664d8a7afe2SAdam Hornacek } catch (FileNotFoundException e) { 665d8a7afe2SAdam Hornacek LOGGER.log(Level.FINE, "Failed to create EftarFileReader: ", e); 666d8a7afe2SAdam Hornacek } 667d8a7afe2SAdam Hornacek } 668d8a7afe2SAdam Hornacek } 669d8a7afe2SAdam Hornacek return eftarReader; 670d8a7afe2SAdam Hornacek } 671d8a7afe2SAdam Hornacek 672d8a7afe2SAdam Hornacek /** 673d8a7afe2SAdam Hornacek * Get the definition tag for the request related file or directory. 674d8a7afe2SAdam Hornacek * 675d8a7afe2SAdam Hornacek * @return an empty string if not found, the tag otherwise. 676d8a7afe2SAdam Hornacek */ 677d8a7afe2SAdam Hornacek public String getDefineTagsIndex() { 678d8a7afe2SAdam Hornacek if (dtag != null) { 679d8a7afe2SAdam Hornacek return dtag; 680d8a7afe2SAdam Hornacek } 681d8a7afe2SAdam Hornacek getEftarReader(); 682d8a7afe2SAdam Hornacek if (eftarReader != null) { 683d8a7afe2SAdam Hornacek try { 684d8a7afe2SAdam Hornacek dtag = eftarReader.get(getPath()); 685d8a7afe2SAdam Hornacek } catch (IOException e) { 686d8a7afe2SAdam Hornacek LOGGER.log(Level.INFO, "Failed to get entry from eftar reader: ", e); 687d8a7afe2SAdam Hornacek } 688d8a7afe2SAdam Hornacek } 689d8a7afe2SAdam Hornacek if (dtag == null) { 690d8a7afe2SAdam Hornacek dtag = ""; 691d8a7afe2SAdam Hornacek } 692d8a7afe2SAdam Hornacek return dtag; 693d8a7afe2SAdam Hornacek } 694d8a7afe2SAdam Hornacek 695d8a7afe2SAdam Hornacek /** 696d8a7afe2SAdam Hornacek * Get the revision parameter {@code r} from the request. 697d8a7afe2SAdam Hornacek * 698d8a7afe2SAdam Hornacek * @return revision if found, an empty string otherwise. 699d8a7afe2SAdam Hornacek */ 700d8a7afe2SAdam Hornacek public String getRequestedRevision() { 701d8a7afe2SAdam Hornacek if (rev == null) { 702d8a7afe2SAdam Hornacek String tmp = Laundromat.launderInput( 703d8a7afe2SAdam Hornacek req.getParameter(QueryParameters.REVISION_PARAM)); 704d8a7afe2SAdam Hornacek rev = (tmp != null && tmp.length() > 0) ? tmp : ""; 705d8a7afe2SAdam Hornacek } 706d8a7afe2SAdam Hornacek return rev; 707d8a7afe2SAdam Hornacek } 708d8a7afe2SAdam Hornacek 709d8a7afe2SAdam Hornacek /** 710d8a7afe2SAdam Hornacek * Check, whether the request related resource has history information. 711d8a7afe2SAdam Hornacek * 712d8a7afe2SAdam Hornacek * @return {@code true} if history is available. 713d8a7afe2SAdam Hornacek * @see HistoryGuru#hasHistory(File) 714d8a7afe2SAdam Hornacek */ 715d8a7afe2SAdam Hornacek public boolean hasHistory() { 716d8a7afe2SAdam Hornacek if (hasHistory == null) { 717d8a7afe2SAdam Hornacek hasHistory = HistoryGuru.getInstance().hasHistory(getResourceFile()); 718d8a7afe2SAdam Hornacek } 719d8a7afe2SAdam Hornacek return hasHistory; 720d8a7afe2SAdam Hornacek } 721d8a7afe2SAdam Hornacek 722d8a7afe2SAdam Hornacek /** 723d8a7afe2SAdam Hornacek * Check, whether annotations are available for the related resource. 724d8a7afe2SAdam Hornacek * 725d8a7afe2SAdam Hornacek * @return {@code true} if annotations are available. 726d8a7afe2SAdam Hornacek */ 727d8a7afe2SAdam Hornacek public boolean hasAnnotations() { 728d8a7afe2SAdam Hornacek if (hasAnnotation == null) { 729d8a7afe2SAdam Hornacek hasAnnotation = !isDir() 730d8a7afe2SAdam Hornacek && HistoryGuru.getInstance().hasHistory(getResourceFile()); 731d8a7afe2SAdam Hornacek } 732d8a7afe2SAdam Hornacek return hasAnnotation; 733d8a7afe2SAdam Hornacek } 734d8a7afe2SAdam Hornacek 735d8a7afe2SAdam Hornacek /** 736d8a7afe2SAdam Hornacek * Check, whether the resource to show should be annotated. 737d8a7afe2SAdam Hornacek * 738d8a7afe2SAdam Hornacek * @return {@code true} if annotation is desired and available. 739d8a7afe2SAdam Hornacek */ 740d8a7afe2SAdam Hornacek public boolean annotate() { 741d8a7afe2SAdam Hornacek if (annotate == null) { 742d8a7afe2SAdam Hornacek annotate = hasAnnotations() 743d8a7afe2SAdam Hornacek && Boolean.parseBoolean(req.getParameter(QueryParameters.ANNOTATION_PARAM)); 744d8a7afe2SAdam Hornacek } 745d8a7afe2SAdam Hornacek return annotate; 746d8a7afe2SAdam Hornacek } 747d8a7afe2SAdam Hornacek 748d8a7afe2SAdam Hornacek /** 749d8a7afe2SAdam Hornacek * Get the annotation for the requested resource. 750d8a7afe2SAdam Hornacek * 751d8a7afe2SAdam Hornacek * @return {@code null} if not available or annotation was not requested, 752d8a7afe2SAdam Hornacek * the cached annotation otherwise. 753d8a7afe2SAdam Hornacek */ 754d8a7afe2SAdam Hornacek public Annotation getAnnotation() { 755d8a7afe2SAdam Hornacek if (isDir() || getResourcePath().equals("/") || !annotate()) { 756d8a7afe2SAdam Hornacek return null; 757d8a7afe2SAdam Hornacek } 758d8a7afe2SAdam Hornacek if (annotation != null) { 759d8a7afe2SAdam Hornacek return annotation; 760d8a7afe2SAdam Hornacek } 761d8a7afe2SAdam Hornacek getRequestedRevision(); 762d8a7afe2SAdam Hornacek try { 763d8a7afe2SAdam Hornacek annotation = HistoryGuru.getInstance().annotate(resourceFile, rev.isEmpty() ? null : rev); 764d8a7afe2SAdam Hornacek } catch (IOException e) { 765d8a7afe2SAdam Hornacek LOGGER.log(Level.WARNING, "Failed to get annotations: ", e); 766d8a7afe2SAdam Hornacek /* ignore */ 767d8a7afe2SAdam Hornacek } 768d8a7afe2SAdam Hornacek return annotation; 769d8a7afe2SAdam Hornacek } 770d8a7afe2SAdam Hornacek 771d8a7afe2SAdam Hornacek /** 772379f387bSVladimir Kotal * Get the {@code path} parameter and display value for "Search only in" option. 773d8a7afe2SAdam Hornacek * 774d8a7afe2SAdam Hornacek * @return always an array of 3 fields, whereby field[0] contains the path 775379f387bSVladimir Kotal * value to use (starts and ends always with a {@link org.opengrok.indexer.index.Indexer#PATH_SEPARATOR}). 776379f387bSVladimir Kotal * Field[1] the contains string to show in the UI. field[2] is set to {@code disabled=""} if the 777d8a7afe2SAdam Hornacek * current path is the "/" directory, otherwise set to an empty string. 778d8a7afe2SAdam Hornacek */ 779d8a7afe2SAdam Hornacek public String[] getSearchOnlyIn() { 780d8a7afe2SAdam Hornacek if (isDir()) { 781d8a7afe2SAdam Hornacek return getPath().length() == 0 782d8a7afe2SAdam Hornacek ? new String[]{"/", "this directory", "disabled=\"\""} 783d8a7afe2SAdam Hornacek : new String[]{getPath(), "this directory", ""}; 784d8a7afe2SAdam Hornacek } 785d8a7afe2SAdam Hornacek String[] res = new String[3]; 786d8a7afe2SAdam Hornacek res[0] = getPath().substring(0, getPath().lastIndexOf(PATH_SEPARATOR) + 1); 787d8a7afe2SAdam Hornacek res[1] = res[0]; 788d8a7afe2SAdam Hornacek res[2] = ""; 789d8a7afe2SAdam Hornacek return res; 790d8a7afe2SAdam Hornacek } 791d8a7afe2SAdam Hornacek 792d8a7afe2SAdam Hornacek /** 793d8a7afe2SAdam Hornacek * Get the project {@link #getPath()} refers to. 794d8a7afe2SAdam Hornacek * 795d8a7afe2SAdam Hornacek * @return {@code null} if not available, the project otherwise. 796d8a7afe2SAdam Hornacek */ 79764819763SVladimir Kotal @Nullable 798d8a7afe2SAdam Hornacek public Project getProject() { 799d8a7afe2SAdam Hornacek return Project.getProject(getResourceFile()); 800d8a7afe2SAdam Hornacek } 801d8a7afe2SAdam Hornacek 802d8a7afe2SAdam Hornacek /** 803d8a7afe2SAdam Hornacek * Same as {@link #getRequestedProjects()} but returns the project names as 804d8a7afe2SAdam Hornacek * a coma separated String. 805d8a7afe2SAdam Hornacek * 806d8a7afe2SAdam Hornacek * @return a possible empty String but never {@code null}. 807d8a7afe2SAdam Hornacek */ 808d8a7afe2SAdam Hornacek public String getRequestedProjectsAsString() { 809d8a7afe2SAdam Hornacek if (requestedProjectsString == null) { 810912cbfdfSAdam Hornacek requestedProjectsString = String.join(",", getRequestedProjects()); 811d8a7afe2SAdam Hornacek } 812d8a7afe2SAdam Hornacek return requestedProjectsString; 813d8a7afe2SAdam Hornacek } 814d8a7afe2SAdam Hornacek 815d8a7afe2SAdam Hornacek /** 816d8a7afe2SAdam Hornacek * Get a reference to a set of requested projects via request parameter 817d8a7afe2SAdam Hornacek * {@code project} or cookies or defaults. 818d8a7afe2SAdam Hornacek * <p> 819d8a7afe2SAdam Hornacek * NOTE: This method assumes, that project names do <b>not</b> contain a 820d8a7afe2SAdam Hornacek * comma (','), since this character is used as name separator! 821d8a7afe2SAdam Hornacek * <p> 822d8a7afe2SAdam Hornacek * It is determined as follows: 823d8a7afe2SAdam Hornacek * <ol> 824d8a7afe2SAdam Hornacek * <li>If there is no project in the configuration an empty set is returned. Otherwise:</li> 825d8a7afe2SAdam Hornacek * <li>If there is only one project in the configuration, 826d8a7afe2SAdam Hornacek * this one gets returned (no matter, what the request actually says). Otherwise</li> 827d8a7afe2SAdam Hornacek * <li>If the request parameter {@code ALL_PROJECT_SEARCH} contains a true value, 828d8a7afe2SAdam Hornacek * all projects are added to searching. Otherwise:</li> 829d8a7afe2SAdam Hornacek * <li>If the request parameter {@code PROJECT_PARAM_NAME} contains any available project, 830d8a7afe2SAdam Hornacek * the set with invalid projects removed gets returned. Otherwise:</li> 831d8a7afe2SAdam Hornacek * <li>If the request parameter {@code GROUP_PARAM_NAME} contains any available group, 832d8a7afe2SAdam Hornacek * then all projects from that group will be added to the result set. Otherwise:</li> 833d8a7afe2SAdam Hornacek * <li>If the request has a cookie with the name {@code OPEN_GROK_PROJECT} 834d8a7afe2SAdam Hornacek * and it contains any available project, 835d8a7afe2SAdam Hornacek * the set with invalid projects removed gets returned. Otherwise:</li> 836d8a7afe2SAdam Hornacek * <li>If a default project is set in the configuration, 837d8a7afe2SAdam Hornacek * this project gets returned. Otherwise:</li> 838d8a7afe2SAdam Hornacek * <li>an empty set</li> 839d8a7afe2SAdam Hornacek * </ol> 840d8a7afe2SAdam Hornacek * 841d8a7afe2SAdam Hornacek * @return a possible empty set of project names but never {@code null}. 842912cbfdfSAdam Hornacek * @see QueryParameters#ALL_PROJECT_SEARCH 843d8a7afe2SAdam Hornacek * @see #PROJECT_PARAM_NAME 844d8a7afe2SAdam Hornacek * @see #GROUP_PARAM_NAME 845d8a7afe2SAdam Hornacek * @see #OPEN_GROK_PROJECT 846d8a7afe2SAdam Hornacek */ 847d8a7afe2SAdam Hornacek public SortedSet<String> getRequestedProjects() { 848d8a7afe2SAdam Hornacek if (requestedProjects == null) { 849912cbfdfSAdam Hornacek requestedProjects = getRequestedProjects( 850912cbfdfSAdam Hornacek QueryParameters.ALL_PROJECT_SEARCH, PROJECT_PARAM_NAME, GROUP_PARAM_NAME, OPEN_GROK_PROJECT); 851d8a7afe2SAdam Hornacek } 852d8a7afe2SAdam Hornacek return requestedProjects; 853d8a7afe2SAdam Hornacek } 854d8a7afe2SAdam Hornacek 855d8a7afe2SAdam Hornacek private static final Pattern COMMA_PATTERN = Pattern.compile(","); 856d8a7afe2SAdam Hornacek 857d8a7afe2SAdam Hornacek private static void splitByComma(String value, List<String> result) { 858d8a7afe2SAdam Hornacek if (value == null || value.length() == 0) { 859d8a7afe2SAdam Hornacek return; 860d8a7afe2SAdam Hornacek } 861d8a7afe2SAdam Hornacek String[] p = COMMA_PATTERN.split(value); 862d8a7afe2SAdam Hornacek for (String p1 : p) { 863d8a7afe2SAdam Hornacek if (p1.length() != 0) { 864d8a7afe2SAdam Hornacek result.add(p1); 865d8a7afe2SAdam Hornacek } 866d8a7afe2SAdam Hornacek } 867d8a7afe2SAdam Hornacek } 868d8a7afe2SAdam Hornacek 869d8a7afe2SAdam Hornacek /** 870d8a7afe2SAdam Hornacek * Get the cookie values for the given name. Splits comma separated values 871d8a7afe2SAdam Hornacek * automatically into a list of Strings. 872d8a7afe2SAdam Hornacek * 873d8a7afe2SAdam Hornacek * @param cookieName name of the cookie. 874d8a7afe2SAdam Hornacek * @return a possible empty list. 875d8a7afe2SAdam Hornacek */ 876d8a7afe2SAdam Hornacek public List<String> getCookieVals(String cookieName) { 877d8a7afe2SAdam Hornacek Cookie[] cookies = req.getCookies(); 878d8a7afe2SAdam Hornacek ArrayList<String> res = new ArrayList<>(); 879d8a7afe2SAdam Hornacek if (cookies != null) { 880d8a7afe2SAdam Hornacek for (int i = cookies.length - 1; i >= 0; i--) { 881d8a7afe2SAdam Hornacek if (cookies[i].getName().equals(cookieName)) { 882c6f0939bSAdam Hornacek String value = URLDecoder.decode(cookies[i].getValue(), StandardCharsets.UTF_8); 883d8a7afe2SAdam Hornacek splitByComma(value, res); 884d8a7afe2SAdam Hornacek } 885d8a7afe2SAdam Hornacek } 886d8a7afe2SAdam Hornacek } 887d8a7afe2SAdam Hornacek return res; 888d8a7afe2SAdam Hornacek } 889d8a7afe2SAdam Hornacek 890d8a7afe2SAdam Hornacek /** 891d8a7afe2SAdam Hornacek * Get the parameter values for the given name. Splits comma separated 892d8a7afe2SAdam Hornacek * values automatically into a list of Strings. 893d8a7afe2SAdam Hornacek * 894d8a7afe2SAdam Hornacek * @param paramName name of the parameter. 895d8a7afe2SAdam Hornacek * @return a possible empty list. 896d8a7afe2SAdam Hornacek */ 897379f387bSVladimir Kotal private List<String> getParameterValues(String paramName) { 898379f387bSVladimir Kotal String[] parameterValues = req.getParameterValues(paramName); 899d8a7afe2SAdam Hornacek List<String> res = new ArrayList<>(); 900379f387bSVladimir Kotal if (parameterValues != null) { 901379f387bSVladimir Kotal for (int i = parameterValues.length - 1; i >= 0; i--) { 902379f387bSVladimir Kotal splitByComma(parameterValues[i], res); 903d8a7afe2SAdam Hornacek } 904d8a7afe2SAdam Hornacek } 905d8a7afe2SAdam Hornacek return res; 906d8a7afe2SAdam Hornacek } 907d8a7afe2SAdam Hornacek 908d8a7afe2SAdam Hornacek /** 909d8a7afe2SAdam Hornacek * Same as {@link #getRequestedProjects()}, but with a variable cookieName 910d8a7afe2SAdam Hornacek * and parameter name. 911d8a7afe2SAdam Hornacek * 912d8a7afe2SAdam Hornacek * @param searchAllParamName the name of the request parameter corresponding to search all projects. 913d8a7afe2SAdam Hornacek * @param projectParamName the name of the request parameter corresponding to a project name. 914d8a7afe2SAdam Hornacek * @param groupParamName the name of the request parameter corresponding to a group name 915d8a7afe2SAdam Hornacek * @param cookieName name of the cookie which possible contains project 916d8a7afe2SAdam Hornacek * names used as fallback 917d8a7afe2SAdam Hornacek * @return set of project names. Possibly empty set but never {@code null}. 918d8a7afe2SAdam Hornacek */ 919d8a7afe2SAdam Hornacek protected SortedSet<String> getRequestedProjects( 920d8a7afe2SAdam Hornacek String searchAllParamName, 921d8a7afe2SAdam Hornacek String projectParamName, 922d8a7afe2SAdam Hornacek String groupParamName, 923d8a7afe2SAdam Hornacek String cookieName 924d8a7afe2SAdam Hornacek ) { 925d8a7afe2SAdam Hornacek 926d8a7afe2SAdam Hornacek TreeSet<String> projectNames = new TreeSet<>(); 927d8a7afe2SAdam Hornacek List<Project> projects = getEnv().getProjectList(); 928d8a7afe2SAdam Hornacek 929d8a7afe2SAdam Hornacek if (Boolean.parseBoolean(req.getParameter(searchAllParamName))) { 930d8a7afe2SAdam Hornacek return getProjectHelper() 931d8a7afe2SAdam Hornacek .getAllProjects() 932d8a7afe2SAdam Hornacek .stream() 933d8a7afe2SAdam Hornacek .map(Project::getName) 934d8a7afe2SAdam Hornacek .collect(Collectors.toCollection(TreeSet::new)); 935d8a7afe2SAdam Hornacek } 936d8a7afe2SAdam Hornacek 937d8a7afe2SAdam Hornacek // Use a project determined directly from the URL 938d8a7afe2SAdam Hornacek if (getProject() != null && getProject().isIndexed()) { 939d8a7afe2SAdam Hornacek projectNames.add(getProject().getName()); 940d8a7afe2SAdam Hornacek return projectNames; 941d8a7afe2SAdam Hornacek } 942d8a7afe2SAdam Hornacek 943d8a7afe2SAdam Hornacek // Use a project if there is just a single project. 944d8a7afe2SAdam Hornacek if (projects.size() == 1) { 945d8a7afe2SAdam Hornacek Project p = projects.get(0); 946d8a7afe2SAdam Hornacek if (p.isIndexed() && authFramework.isAllowed(req, p)) { 947d8a7afe2SAdam Hornacek projectNames.add(p.getName()); 948d8a7afe2SAdam Hornacek } 949d8a7afe2SAdam Hornacek return projectNames; 950d8a7afe2SAdam Hornacek } 951d8a7afe2SAdam Hornacek 952d8a7afe2SAdam Hornacek // Add all projects which match the project parameter name values/ 953379f387bSVladimir Kotal List<String> names = getParameterValues(projectParamName); 954d8a7afe2SAdam Hornacek for (String projectName : names) { 955d8a7afe2SAdam Hornacek Project project = Project.getByName(projectName); 956d8a7afe2SAdam Hornacek if (project != null && project.isIndexed() && authFramework.isAllowed(req, project)) { 957d8a7afe2SAdam Hornacek projectNames.add(projectName); 958d8a7afe2SAdam Hornacek } 959d8a7afe2SAdam Hornacek } 960d8a7afe2SAdam Hornacek 961d8a7afe2SAdam Hornacek // Add all projects which are part of a group that matches the group parameter name. 962379f387bSVladimir Kotal names = getParameterValues(groupParamName); 963d8a7afe2SAdam Hornacek for (String groupName : names) { 964d8a7afe2SAdam Hornacek Group group = Group.getByName(groupName); 965d8a7afe2SAdam Hornacek if (group != null) { 966d8a7afe2SAdam Hornacek projectNames.addAll(getProjectHelper().getAllGrouped(group) 967d8a7afe2SAdam Hornacek .stream() 968c6f0939bSAdam Hornacek .filter(Project::isIndexed) 969d8a7afe2SAdam Hornacek .map(Project::getName) 970d8a7afe2SAdam Hornacek .collect(Collectors.toSet())); 971d8a7afe2SAdam Hornacek } 972d8a7afe2SAdam Hornacek } 973d8a7afe2SAdam Hornacek 974d8a7afe2SAdam Hornacek // Add projects based on cookie. 975d8a7afe2SAdam Hornacek if (projectNames.isEmpty() && getIntParam(QueryParameters.NUM_SELECTED_PARAM, -1) != 0) { 976d8a7afe2SAdam Hornacek List<String> cookies = getCookieVals(cookieName); 977d8a7afe2SAdam Hornacek for (String s : cookies) { 978d8a7afe2SAdam Hornacek Project x = Project.getByName(s); 979d8a7afe2SAdam Hornacek if (x != null && x.isIndexed() && authFramework.isAllowed(req, x)) { 980d8a7afe2SAdam Hornacek projectNames.add(s); 981d8a7afe2SAdam Hornacek } 982d8a7afe2SAdam Hornacek } 983d8a7afe2SAdam Hornacek } 984d8a7afe2SAdam Hornacek 985d8a7afe2SAdam Hornacek // Add default projects. 986d8a7afe2SAdam Hornacek if (projectNames.isEmpty()) { 987d8a7afe2SAdam Hornacek Set<Project> defaultProjects = env.getDefaultProjects(); 988d8a7afe2SAdam Hornacek if (defaultProjects != null) { 989d8a7afe2SAdam Hornacek for (Project project : defaultProjects) { 990d8a7afe2SAdam Hornacek if (project.isIndexed() && authFramework.isAllowed(req, project)) { 991d8a7afe2SAdam Hornacek projectNames.add(project.getName()); 992d8a7afe2SAdam Hornacek } 993d8a7afe2SAdam Hornacek } 994d8a7afe2SAdam Hornacek } 995d8a7afe2SAdam Hornacek } 996d8a7afe2SAdam Hornacek 997d8a7afe2SAdam Hornacek return projectNames; 998d8a7afe2SAdam Hornacek } 999d8a7afe2SAdam Hornacek 1000d8a7afe2SAdam Hornacek public ProjectHelper getProjectHelper() { 1001d8a7afe2SAdam Hornacek return ProjectHelper.getInstance(this); 1002d8a7afe2SAdam Hornacek } 1003d8a7afe2SAdam Hornacek 1004d8a7afe2SAdam Hornacek /** 1005d8a7afe2SAdam Hornacek * Set the page title to use. 1006d8a7afe2SAdam Hornacek * 1007d8a7afe2SAdam Hornacek * @param title title to set (might be {@code null}). 1008d8a7afe2SAdam Hornacek */ 1009d8a7afe2SAdam Hornacek public void setTitle(String title) { 1010d8a7afe2SAdam Hornacek pageTitle = title; 1011d8a7afe2SAdam Hornacek } 1012d8a7afe2SAdam Hornacek 1013d8a7afe2SAdam Hornacek /** 1014d8a7afe2SAdam Hornacek * Get the page title to use. 1015d8a7afe2SAdam Hornacek * 1016d8a7afe2SAdam Hornacek * @return {@code null} if not set, the page title otherwise. 1017d8a7afe2SAdam Hornacek */ 1018d8a7afe2SAdam Hornacek public String getTitle() { 1019d8a7afe2SAdam Hornacek return pageTitle; 1020d8a7afe2SAdam Hornacek } 1021d8a7afe2SAdam Hornacek 1022d8a7afe2SAdam Hornacek /** 1023d8a7afe2SAdam Hornacek * Get the base path to use to refer to CSS stylesheets and related 1024d8a7afe2SAdam Hornacek * resources. Usually used to create links. 1025d8a7afe2SAdam Hornacek * 1026d8a7afe2SAdam Hornacek * @return the appropriate application directory prefixed with the 1027d8a7afe2SAdam Hornacek * application's context path (e.g. "/source/default"). 1028d8a7afe2SAdam Hornacek * @see HttpServletRequest#getContextPath() 1029d8a7afe2SAdam Hornacek * @see RuntimeEnvironment#getWebappLAF() 1030d8a7afe2SAdam Hornacek */ 1031d8a7afe2SAdam Hornacek public String getCssDir() { 1032d8a7afe2SAdam Hornacek return req.getContextPath() + PATH_SEPARATOR + getEnv().getWebappLAF(); 1033d8a7afe2SAdam Hornacek } 1034d8a7afe2SAdam Hornacek 1035d8a7afe2SAdam Hornacek /** 1036d8a7afe2SAdam Hornacek * Get the current runtime environment. 1037d8a7afe2SAdam Hornacek * 1038d8a7afe2SAdam Hornacek * @return the runtime env. 1039d8a7afe2SAdam Hornacek * @see RuntimeEnvironment#getInstance() 1040d8a7afe2SAdam Hornacek */ 1041d8a7afe2SAdam Hornacek public RuntimeEnvironment getEnv() { 1042d8a7afe2SAdam Hornacek if (env == null) { 1043d8a7afe2SAdam Hornacek env = RuntimeEnvironment.getInstance(); 1044d8a7afe2SAdam Hornacek } 1045d8a7afe2SAdam Hornacek return env; 1046d8a7afe2SAdam Hornacek } 1047d8a7afe2SAdam Hornacek 1048d8a7afe2SAdam Hornacek /** 1049d8a7afe2SAdam Hornacek * Get the name patterns used to determine, whether a file should be 1050d8a7afe2SAdam Hornacek * ignored. 1051d8a7afe2SAdam Hornacek * 1052d8a7afe2SAdam Hornacek * @return the corresponding value from the current runtime config.. 1053d8a7afe2SAdam Hornacek */ 1054d8a7afe2SAdam Hornacek public IgnoredNames getIgnoredNames() { 1055d8a7afe2SAdam Hornacek if (ignoredNames == null) { 1056d8a7afe2SAdam Hornacek ignoredNames = getEnv().getIgnoredNames(); 1057d8a7afe2SAdam Hornacek } 1058d8a7afe2SAdam Hornacek return ignoredNames; 1059d8a7afe2SAdam Hornacek } 1060d8a7afe2SAdam Hornacek 1061d8a7afe2SAdam Hornacek /** 1062d8a7afe2SAdam Hornacek * Get the canonical path to root of the source tree. File separators are 1063d8a7afe2SAdam Hornacek * replaced with a {@link org.opengrok.indexer.index.Indexer#PATH_SEPARATOR}. 1064d8a7afe2SAdam Hornacek * 1065d8a7afe2SAdam Hornacek * @return The on disk source root directory. 1066d8a7afe2SAdam Hornacek * @see RuntimeEnvironment#getSourceRootPath() 1067d8a7afe2SAdam Hornacek */ 1068d8a7afe2SAdam Hornacek public String getSourceRootPath() { 1069d8a7afe2SAdam Hornacek if (sourceRootPath == null) { 1070d8a7afe2SAdam Hornacek String srcpath = getEnv().getSourceRootPath(); 1071d8a7afe2SAdam Hornacek if (srcpath != null) { 1072d8a7afe2SAdam Hornacek sourceRootPath = srcpath.replace(File.separatorChar, PATH_SEPARATOR); 1073d8a7afe2SAdam Hornacek } 1074d8a7afe2SAdam Hornacek } 1075d8a7afe2SAdam Hornacek return sourceRootPath; 1076d8a7afe2SAdam Hornacek } 1077d8a7afe2SAdam Hornacek 1078d8a7afe2SAdam Hornacek /** 1079d8a7afe2SAdam Hornacek * Get the prefix for the related request. 1080d8a7afe2SAdam Hornacek * 1081d8a7afe2SAdam Hornacek * @return {@link Prefix#UNKNOWN} if the servlet path matches any known 1082d8a7afe2SAdam Hornacek * prefix, the prefix otherwise. 1083d8a7afe2SAdam Hornacek */ 1084d8a7afe2SAdam Hornacek public Prefix getPrefix() { 1085d8a7afe2SAdam Hornacek if (prefix == null) { 1086d8a7afe2SAdam Hornacek prefix = Prefix.get(req.getServletPath()); 1087d8a7afe2SAdam Hornacek } 1088d8a7afe2SAdam Hornacek return prefix; 1089d8a7afe2SAdam Hornacek } 1090d8a7afe2SAdam Hornacek 1091d8a7afe2SAdam Hornacek /** 1092d8a7afe2SAdam Hornacek * Get the canonical path of the related resource relative to the source 1093379f387bSVladimir Kotal * root directory (used file separators are all {@link org.opengrok.indexer.index.Indexer#PATH_SEPARATOR}). 1094379f387bSVladimir Kotal * No check is made, whether the obtained path is really an accessible resource on disk. 1095d8a7afe2SAdam Hornacek * 1096d8a7afe2SAdam Hornacek * @see HttpServletRequest#getPathInfo() 1097d8a7afe2SAdam Hornacek * @return a possible empty String (denotes the source root directory) but 1098d8a7afe2SAdam Hornacek * not {@code null}. 1099d8a7afe2SAdam Hornacek */ 1100d8a7afe2SAdam Hornacek public String getPath() { 1101d8a7afe2SAdam Hornacek if (path == null) { 1102c6d5bfc3SVladimir Kotal path = Util.getCanonicalPath(Laundromat.launderInput(req.getPathInfo()), PATH_SEPARATOR); 1103d8a7afe2SAdam Hornacek if (PATH_SEPARATOR_STRING.equals(path)) { 1104d8a7afe2SAdam Hornacek path = ""; 1105d8a7afe2SAdam Hornacek } 1106d8a7afe2SAdam Hornacek } 1107d8a7afe2SAdam Hornacek return path; 1108d8a7afe2SAdam Hornacek } 1109d8a7afe2SAdam Hornacek 111057acd71dSVladimir Kotal /** 1111379f387bSVladimir Kotal * @return true if file/directory corresponding to the request path exists however is unreadable, false otherwise 111257acd71dSVladimir Kotal */ 11139554a08aSVladimir Kotal public boolean isUnreadable() { 11149554a08aSVladimir Kotal File f = new File(getSourceRootPath(), getPath()); 11159554a08aSVladimir Kotal return f.exists() && !f.canRead(); 11169554a08aSVladimir Kotal } 11179554a08aSVladimir Kotal 1118d8a7afe2SAdam Hornacek /** 1119d8a7afe2SAdam Hornacek * Get the on disk file for the given path. 1120d8a7afe2SAdam Hornacek * 1121d8a7afe2SAdam Hornacek * NOTE: If a repository contains hard or symbolic links, the returned file 1122379f387bSVladimir Kotal * may finally point to a file outside the source root directory. 1123d8a7afe2SAdam Hornacek * 1124d8a7afe2SAdam Hornacek * @param path the path to the file relatively to the source root 1125d8a7afe2SAdam Hornacek * @return null if the related file or directory is not 1126d8a7afe2SAdam Hornacek * available (can not be find below the source root directory), the readable 1127d8a7afe2SAdam Hornacek * file or directory otherwise. 1128d8a7afe2SAdam Hornacek * @see #getSourceRootPath() 1129d8a7afe2SAdam Hornacek */ 1130d8a7afe2SAdam Hornacek public File getResourceFile(String path) { 1131d8a7afe2SAdam Hornacek File f; 1132d8a7afe2SAdam Hornacek f = new File(getSourceRootPath(), path); 1133d8a7afe2SAdam Hornacek if (!f.canRead()) { 1134d8a7afe2SAdam Hornacek return null; 1135d8a7afe2SAdam Hornacek } 1136d8a7afe2SAdam Hornacek return f; 1137d8a7afe2SAdam Hornacek } 1138d8a7afe2SAdam Hornacek 1139d8a7afe2SAdam Hornacek /** 1140d8a7afe2SAdam Hornacek * Get the on disk file to the request related file or directory. 1141d8a7afe2SAdam Hornacek * 1142d8a7afe2SAdam Hornacek * NOTE: If a repository contains hard or symbolic links, the returned file 1143379f387bSVladimir Kotal * may finally point to a file outside the source root directory. 1144d8a7afe2SAdam Hornacek * 1145379f387bSVladimir Kotal * @return {@code new File({@link org.opengrok.indexer.index.Indexer#PATH_SEPARATOR_STRING })} 1146379f387bSVladimir Kotal * if the related file or directory is not available (can not be find below the source root directory), 1147379f387bSVladimir Kotal * the readable file or directory otherwise. 1148d8a7afe2SAdam Hornacek * @see #getSourceRootPath() 1149d8a7afe2SAdam Hornacek * @see #getPath() 1150d8a7afe2SAdam Hornacek */ 1151d8a7afe2SAdam Hornacek public File getResourceFile() { 1152d8a7afe2SAdam Hornacek if (resourceFile == null) { 1153d8a7afe2SAdam Hornacek resourceFile = getResourceFile(getPath()); 1154d8a7afe2SAdam Hornacek if (resourceFile == null) { 1155d8a7afe2SAdam Hornacek resourceFile = new File(PATH_SEPARATOR_STRING); 1156d8a7afe2SAdam Hornacek } 1157d8a7afe2SAdam Hornacek } 1158d8a7afe2SAdam Hornacek return resourceFile; 1159d8a7afe2SAdam Hornacek } 1160d8a7afe2SAdam Hornacek 1161d8a7afe2SAdam Hornacek /** 1162d8a7afe2SAdam Hornacek * Get the canonical on disk path to the request related file or directory 1163d8a7afe2SAdam Hornacek * with all file separators replaced by a {@link org.opengrok.indexer.index.Indexer#PATH_SEPARATOR}. 1164d8a7afe2SAdam Hornacek * 1165379f387bSVladimir Kotal * @return {@link org.opengrok.indexer.index.Indexer#PATH_SEPARATOR_STRING} if the evaluated path is invalid 1166379f387bSVladimir Kotal * or outside the source root directory, otherwise the path to the readable file or directory. 1167d8a7afe2SAdam Hornacek * @see #getResourceFile() 1168d8a7afe2SAdam Hornacek */ 1169d8a7afe2SAdam Hornacek public String getResourcePath() { 1170d8a7afe2SAdam Hornacek if (resourcePath == null) { 1171d8a7afe2SAdam Hornacek resourcePath = Util.fixPathIfWindows(getResourceFile().getPath()); 1172d8a7afe2SAdam Hornacek } 1173d8a7afe2SAdam Hornacek return resourcePath; 1174d8a7afe2SAdam Hornacek } 1175d8a7afe2SAdam Hornacek 1176d8a7afe2SAdam Hornacek /** 1177d8a7afe2SAdam Hornacek * Check, whether the related request resource matches a valid file or 1178d8a7afe2SAdam Hornacek * directory below the source root directory and whether it matches an 1179d8a7afe2SAdam Hornacek * ignored pattern. 1180d8a7afe2SAdam Hornacek * 1181379f387bSVladimir Kotal * @return {@code true} if the related resource does not exist or should be ignored. 1182d8a7afe2SAdam Hornacek * @see #getIgnoredNames() 1183d8a7afe2SAdam Hornacek * @see #getResourcePath() 1184d8a7afe2SAdam Hornacek */ 1185d8a7afe2SAdam Hornacek public boolean resourceNotAvailable() { 1186d8a7afe2SAdam Hornacek getIgnoredNames(); 1187d8a7afe2SAdam Hornacek return getResourcePath().equals(PATH_SEPARATOR_STRING) || ignoredNames.ignore(getPath()) 1188d8a7afe2SAdam Hornacek || ignoredNames.ignore(resourceFile.getParentFile()) 1189d8a7afe2SAdam Hornacek || ignoredNames.ignore(resourceFile); 1190d8a7afe2SAdam Hornacek } 1191d8a7afe2SAdam Hornacek 1192d8a7afe2SAdam Hornacek /** 1193d8a7afe2SAdam Hornacek * Check, whether the request related path represents a directory. 1194d8a7afe2SAdam Hornacek * 1195d8a7afe2SAdam Hornacek * @return {@code true} if directory related request 1196d8a7afe2SAdam Hornacek */ 1197d8a7afe2SAdam Hornacek public boolean isDir() { 1198d8a7afe2SAdam Hornacek if (isDir == null) { 1199d8a7afe2SAdam Hornacek isDir = getResourceFile().isDirectory(); 1200d8a7afe2SAdam Hornacek } 1201d8a7afe2SAdam Hornacek return isDir; 1202d8a7afe2SAdam Hornacek } 1203d8a7afe2SAdam Hornacek 1204d8a7afe2SAdam Hornacek private static String trailingSlash(String path) { 1205d8a7afe2SAdam Hornacek return path.length() == 0 || path.charAt(path.length() - 1) != PATH_SEPARATOR 1206d8a7afe2SAdam Hornacek ? PATH_SEPARATOR_STRING 1207d8a7afe2SAdam Hornacek : ""; 1208d8a7afe2SAdam Hornacek } 1209d8a7afe2SAdam Hornacek 12107b79f34fSVladimir Kotal private File checkFileInner(File file, File dir, String name) { 12117b79f34fSVladimir Kotal File f = new File(dir, name); 12127b79f34fSVladimir Kotal if (f.exists() && f.isFile()) { 12137b79f34fSVladimir Kotal if (f.lastModified() >= file.lastModified()) { 12147b79f34fSVladimir Kotal return f; 12157b79f34fSVladimir Kotal } else { 12167b79f34fSVladimir Kotal LOGGER.log(Level.WARNING, "file ''{0}'' is newer than ''{1}''", new Object[]{file, f}); 12177b79f34fSVladimir Kotal } 12187b79f34fSVladimir Kotal } 12197b79f34fSVladimir Kotal 12207b79f34fSVladimir Kotal return null; 12217b79f34fSVladimir Kotal } 12227b79f34fSVladimir Kotal 122392f4530fSVladimir Kotal private File checkFile(File file, File dir, String name, boolean compressed) { 1224d8a7afe2SAdam Hornacek File f; 1225d8a7afe2SAdam Hornacek if (compressed) { 12267b79f34fSVladimir Kotal f = checkFileInner(file, dir, TandemPath.join(name, ".gz")); 12277b79f34fSVladimir Kotal if (f != null) { 1228d8a7afe2SAdam Hornacek return f; 1229d8a7afe2SAdam Hornacek } 1230d8a7afe2SAdam Hornacek } 12317b79f34fSVladimir Kotal 12327b79f34fSVladimir Kotal return checkFileInner(file, dir, name); 1233d8a7afe2SAdam Hornacek } 1234d8a7afe2SAdam Hornacek 1235d8a7afe2SAdam Hornacek private File checkFileResolve(File dir, String name, boolean compressed) { 1236d8a7afe2SAdam Hornacek File lresourceFile = new File(getSourceRootPath() + getPath(), name); 1237d8a7afe2SAdam Hornacek if (!lresourceFile.canRead()) { 1238d8a7afe2SAdam Hornacek lresourceFile = new File(PATH_SEPARATOR_STRING); 1239d8a7afe2SAdam Hornacek } 124092f4530fSVladimir Kotal 124192f4530fSVladimir Kotal return checkFile(lresourceFile, dir, name, compressed); 1242d8a7afe2SAdam Hornacek } 1243d8a7afe2SAdam Hornacek 1244d8a7afe2SAdam Hornacek /** 1245d8a7afe2SAdam Hornacek * Find the files with the given names in the {@link #getPath()} directory 1246379f387bSVladimir Kotal * relative to the cross-file directory of the opengrok data directory. It is 1247d8a7afe2SAdam Hornacek * tried to find the compressed file first by appending the file extension 1248d8a7afe2SAdam Hornacek * ".gz" to the filename. If that fails or an uncompressed version of the 1249d8a7afe2SAdam Hornacek * file is younger than its compressed version, the uncompressed file gets 1250d8a7afe2SAdam Hornacek * used. 1251d8a7afe2SAdam Hornacek * 1252d8a7afe2SAdam Hornacek * @param filenames filenames to lookup. 1253d8a7afe2SAdam Hornacek * @return an empty array if the related directory does not exist or the 1254d8a7afe2SAdam Hornacek * given list is {@code null} or empty, otherwise an array, which may 1255d8a7afe2SAdam Hornacek * contain {@code null} entries (when the related file could not be found) 1256d8a7afe2SAdam Hornacek * having the same order as the given list. 1257d8a7afe2SAdam Hornacek */ 1258d8a7afe2SAdam Hornacek public File[] findDataFiles(List<String> filenames) { 1259d8a7afe2SAdam Hornacek if (filenames == null || filenames.isEmpty()) { 1260d8a7afe2SAdam Hornacek return new File[0]; 1261d8a7afe2SAdam Hornacek } 1262d8a7afe2SAdam Hornacek File[] res = new File[filenames.size()]; 1263d8a7afe2SAdam Hornacek File dir = new File(getEnv().getDataRootPath() + Prefix.XREF_P + getPath()); 1264d8a7afe2SAdam Hornacek if (dir.exists() && dir.isDirectory()) { 1265d8a7afe2SAdam Hornacek getResourceFile(); 1266d8a7afe2SAdam Hornacek boolean compressed = getEnv().isCompressXref(); 1267d8a7afe2SAdam Hornacek for (int i = res.length - 1; i >= 0; i--) { 1268d8a7afe2SAdam Hornacek res[i] = checkFileResolve(dir, filenames.get(i), compressed); 1269d8a7afe2SAdam Hornacek } 1270d8a7afe2SAdam Hornacek } 1271d8a7afe2SAdam Hornacek return res; 1272d8a7afe2SAdam Hornacek } 1273d8a7afe2SAdam Hornacek 1274d8a7afe2SAdam Hornacek /** 1275379f387bSVladimir Kotal * Lookup the file {@link #getPath()} relative to the cross-file directory of 1276d8a7afe2SAdam Hornacek * the opengrok data directory. It is tried to find the compressed file 1277d8a7afe2SAdam Hornacek * first by appending the file extension ".gz" to the filename. If that 1278d8a7afe2SAdam Hornacek * fails or an uncompressed version of the file is younger than its 1279d8a7afe2SAdam Hornacek * compressed version, the uncompressed file gets used. 1280d8a7afe2SAdam Hornacek * 1281d8a7afe2SAdam Hornacek * @return {@code null} if not found, the file otherwise. 1282d8a7afe2SAdam Hornacek */ 1283d8a7afe2SAdam Hornacek public File findDataFile() { 128492f4530fSVladimir Kotal return checkFile(resourceFile, new File(getEnv().getDataRootPath() + Prefix.XREF_P), 1285d8a7afe2SAdam Hornacek getPath(), env.isCompressXref()); 1286d8a7afe2SAdam Hornacek } 1287d8a7afe2SAdam Hornacek 128836b6a6f8SVladimir Kotal /** 128936b6a6f8SVladimir Kotal * @return last revision string for {@code file} or null 129036b6a6f8SVladimir Kotal */ 1291a964b78cSVladimir Kotal @Nullable 1292d8a7afe2SAdam Hornacek public String getLatestRevision() { 1293d8a7afe2SAdam Hornacek if (!getEnv().isHistoryEnabled()) { 1294094a9b1eSVladimir Kotal LOGGER.log(Level.FINE, "will not get latest revision for ''{0}'' as history is disabled", 1295094a9b1eSVladimir Kotal getResourceFile()); 1296d8a7afe2SAdam Hornacek return null; 1297d8a7afe2SAdam Hornacek } 1298d8a7afe2SAdam Hornacek 1299a964b78cSVladimir Kotal // Try getting the history revision from the index first. 130036b6a6f8SVladimir Kotal String lastRev = getLastRevFromIndex(); 130136b6a6f8SVladimir Kotal if (lastRev != null) { 1302094a9b1eSVladimir Kotal LOGGER.log(Level.FINEST, "got last revision of ''{0}'' from the index", getResourceFile()); 130336b6a6f8SVladimir Kotal return lastRev; 130436b6a6f8SVladimir Kotal } 130536b6a6f8SVladimir Kotal 1306a964b78cSVladimir Kotal // If this is older index, fallback to the history (either fetch from history cache or retrieve from 1307a964b78cSVladimir Kotal // the repository directly). 1308a903f806SVladimir Kotal try { 130936b6a6f8SVladimir Kotal return getLastRevFromHistory(); 1310a903f806SVladimir Kotal } catch (HistoryException e) { 1311094a9b1eSVladimir Kotal LOGGER.log(Level.WARNING, "cannot get latest revision for ''{0}'' using history", getPath()); 1312a903f806SVladimir Kotal return null; 1313a903f806SVladimir Kotal } 131436b6a6f8SVladimir Kotal } 131536b6a6f8SVladimir Kotal 131636b6a6f8SVladimir Kotal @Nullable 1317a903f806SVladimir Kotal private String getLastRevFromHistory() throws HistoryException { 1318a964b78cSVladimir Kotal File file = new File(getEnv().getSourceRootFile(), getPath()); 1319a964b78cSVladimir Kotal HistoryEntry he = HistoryGuru.getInstance().getLastHistoryEntry(file, true); 1320a903f806SVladimir Kotal if (he != null) { 1321d8a7afe2SAdam Hornacek return he.getRevision(); 1322d8a7afe2SAdam Hornacek } 1323d8a7afe2SAdam Hornacek 1324a903f806SVladimir Kotal return null; 1325a903f806SVladimir Kotal } 1326a903f806SVladimir Kotal 13278ff8eecbSVladimir Kotal /** 13288ff8eecbSVladimir Kotal * Retrieve last revision from the document matching the resource file (if any). 1329df712980SVladimir Kotal * @return last revision or {@code null} if the document cannot be found, is out of sync 1330df712980SVladimir Kotal * w.r.t. last modified time of the file or the last commit ID is not stored in the document. 13318ff8eecbSVladimir Kotal */ 133236b6a6f8SVladimir Kotal @Nullable 1333*c4b39070SVladimir Kotal @VisibleForTesting 1334*c4b39070SVladimir Kotal String getLastRevFromIndex() { 133536b6a6f8SVladimir Kotal Document doc = null; 133636b6a6f8SVladimir Kotal try { 133736b6a6f8SVladimir Kotal doc = IndexDatabase.getDocument(getResourceFile()); 1338df712980SVladimir Kotal } catch (Exception e) { 1339df712980SVladimir Kotal LOGGER.log(Level.WARNING, String.format("cannot get document for %s", path), e); 1340df712980SVladimir Kotal } 1341df712980SVladimir Kotal 1342df712980SVladimir Kotal String lastRev = null; 13438ff8eecbSVladimir Kotal if (doc != null) { 1344df712980SVladimir Kotal // There is no point of checking the date if the LASTREV field is not present. 1345df712980SVladimir Kotal lastRev = doc.get(QueryBuilder.LASTREV); 1346df712980SVladimir Kotal if (lastRev != null) { 1347df712980SVladimir Kotal Date docDate; 1348df712980SVladimir Kotal try { 1349df712980SVladimir Kotal docDate = DateTools.stringToDate(doc.get(QueryBuilder.DATE)); 1350df712980SVladimir Kotal } catch (ParseException e) { 1351df712980SVladimir Kotal LOGGER.log(Level.WARNING, String.format("cannot get date from the document %s", doc), e); 1352df712980SVladimir Kotal return null; 1353df712980SVladimir Kotal } 13548ff8eecbSVladimir Kotal Date fileDate = new Date(getResourceFile().lastModified()); 13558ff8eecbSVladimir Kotal if (docDate.compareTo(fileDate) < 0) { 1356379f387bSVladimir Kotal LOGGER.log(Level.FINER, "document for ''{0}'' is out of sync", getResourceFile()); 13578ff8eecbSVladimir Kotal return null; 13588ff8eecbSVladimir Kotal } 13598ff8eecbSVladimir Kotal } 136036b6a6f8SVladimir Kotal } 136136b6a6f8SVladimir Kotal 1362df712980SVladimir Kotal return lastRev; 136336b6a6f8SVladimir Kotal } 136436b6a6f8SVladimir Kotal 1365d8a7afe2SAdam Hornacek /** 1366d8a7afe2SAdam Hornacek * Is revision the latest revision ? 1367d8a7afe2SAdam Hornacek * @param rev revision string 1368d8a7afe2SAdam Hornacek * @return true if latest revision, false otherwise 1369d8a7afe2SAdam Hornacek */ 1370d8a7afe2SAdam Hornacek public boolean isLatestRevision(String rev) { 1371d8a7afe2SAdam Hornacek return rev.equals(getLatestRevision()); 1372d8a7afe2SAdam Hornacek } 1373d8a7afe2SAdam Hornacek 1374d8a7afe2SAdam Hornacek /** 1375379f387bSVladimir Kotal * Get the location of cross-reference for given file containing the given revision. 1376d8a7afe2SAdam Hornacek * @param revStr defined revision string 1377d8a7afe2SAdam Hornacek * @return location to redirect to 1378d8a7afe2SAdam Hornacek */ 1379d8a7afe2SAdam Hornacek public String getRevisionLocation(String revStr) { 1380d8a7afe2SAdam Hornacek StringBuilder sb = new StringBuilder(); 1381d8a7afe2SAdam Hornacek 1382d8a7afe2SAdam Hornacek sb.append(req.getContextPath()); 1383d8a7afe2SAdam Hornacek sb.append(Prefix.XREF_P); 1384d6df19e1SAdam Hornacek sb.append(Util.uriEncodePath(getPath())); 1385d8a7afe2SAdam Hornacek sb.append("?"); 1386d8a7afe2SAdam Hornacek sb.append(QueryParameters.REVISION_PARAM_EQ); 1387d6df19e1SAdam Hornacek sb.append(Util.uriEncode(revStr)); 1388d8a7afe2SAdam Hornacek 1389d8a7afe2SAdam Hornacek if (req.getQueryString() != null) { 1390d8a7afe2SAdam Hornacek sb.append("&"); 1391d8a7afe2SAdam Hornacek sb.append(req.getQueryString()); 1392d8a7afe2SAdam Hornacek } 1393d8a7afe2SAdam Hornacek if (fragmentIdentifier != null) { 1394d6df19e1SAdam Hornacek String anchor = Util.uriEncode(fragmentIdentifier); 1395d8a7afe2SAdam Hornacek 1396d8a7afe2SAdam Hornacek String reqFrag = req.getParameter(QueryParameters.FRAGMENT_IDENTIFIER_PARAM); 1397d8a7afe2SAdam Hornacek if (reqFrag == null || reqFrag.isEmpty()) { 1398d8a7afe2SAdam Hornacek /* 1399d8a7afe2SAdam Hornacek * We've determined that the fragmentIdentifier field must have 1400d8a7afe2SAdam Hornacek * been set to augment request parameters. Now include it 1401d8a7afe2SAdam Hornacek * explicitly in the next request parameters. 1402d8a7afe2SAdam Hornacek */ 1403d8a7afe2SAdam Hornacek sb.append("&"); 1404d8a7afe2SAdam Hornacek sb.append(QueryParameters.FRAGMENT_IDENTIFIER_PARAM_EQ); 1405d8a7afe2SAdam Hornacek sb.append(anchor); 1406d8a7afe2SAdam Hornacek } 1407d8a7afe2SAdam Hornacek sb.append("#"); 1408d8a7afe2SAdam Hornacek sb.append(anchor); 1409d8a7afe2SAdam Hornacek } 1410d8a7afe2SAdam Hornacek 1411d8a7afe2SAdam Hornacek return sb.toString(); 1412d8a7afe2SAdam Hornacek } 1413d8a7afe2SAdam Hornacek 1414d8a7afe2SAdam Hornacek /** 1415d8a7afe2SAdam Hornacek * Get the path the request should be redirected (if any). 1416d8a7afe2SAdam Hornacek * 1417d8a7afe2SAdam Hornacek * @return {@code null} if there is no reason to redirect, the URI encoded 1418d8a7afe2SAdam Hornacek * redirect path to use otherwise. 1419d8a7afe2SAdam Hornacek */ 1420d8a7afe2SAdam Hornacek public String getDirectoryRedirect() { 1421d8a7afe2SAdam Hornacek if (isDir()) { 1422d8a7afe2SAdam Hornacek getPrefix(); 1423379f387bSVladimir Kotal // Redirect /xref -> /xref/ 1424d8a7afe2SAdam Hornacek if (prefix == Prefix.XREF_P 1425d8a7afe2SAdam Hornacek && getUriEncodedPath().isEmpty() 1426d8a7afe2SAdam Hornacek && !req.getRequestURI().endsWith("/")) { 1427d8a7afe2SAdam Hornacek return req.getContextPath() + Prefix.XREF_P + '/'; 1428d8a7afe2SAdam Hornacek } 1429d8a7afe2SAdam Hornacek 1430d8a7afe2SAdam Hornacek if (getPath().length() == 0) { 1431d8a7afe2SAdam Hornacek // => / 1432d8a7afe2SAdam Hornacek return null; 1433d8a7afe2SAdam Hornacek } 1434d8a7afe2SAdam Hornacek 1435d8a7afe2SAdam Hornacek if (prefix != Prefix.XREF_P && prefix != Prefix.HIST_L 1436d8a7afe2SAdam Hornacek && prefix != Prefix.RSS_P) { 1437d8a7afe2SAdam Hornacek // if it is an existing dir perhaps people wanted dir xref 1438d8a7afe2SAdam Hornacek return req.getContextPath() + Prefix.XREF_P 1439d8a7afe2SAdam Hornacek + getUriEncodedPath() + trailingSlash(getPath()); 1440d8a7afe2SAdam Hornacek } 1441d8a7afe2SAdam Hornacek String ts = trailingSlash(getPath()); 1442d8a7afe2SAdam Hornacek if (ts.length() != 0) { 1443d8a7afe2SAdam Hornacek return req.getContextPath() + prefix + getUriEncodedPath() + ts; 1444d8a7afe2SAdam Hornacek } 1445d8a7afe2SAdam Hornacek } 1446d8a7afe2SAdam Hornacek return null; 1447d8a7afe2SAdam Hornacek } 1448d8a7afe2SAdam Hornacek 1449d8a7afe2SAdam Hornacek /** 1450d8a7afe2SAdam Hornacek * Get the URI encoded canonical path to the related file or directory (the 1451d8a7afe2SAdam Hornacek * URI part between the servlet path and the start of the query string). 1452d8a7afe2SAdam Hornacek * 1453379f387bSVladimir Kotal * @return a URI encoded path which might be an empty string but not {@code null}. 1454d8a7afe2SAdam Hornacek * @see #getPath() 1455d8a7afe2SAdam Hornacek */ 1456d8a7afe2SAdam Hornacek public String getUriEncodedPath() { 1457d8a7afe2SAdam Hornacek if (uriEncodedPath == null) { 1458d6df19e1SAdam Hornacek uriEncodedPath = Util.uriEncodePath(getPath()); 1459d8a7afe2SAdam Hornacek } 1460d8a7afe2SAdam Hornacek return uriEncodedPath; 1461d8a7afe2SAdam Hornacek } 1462d8a7afe2SAdam Hornacek 1463d8a7afe2SAdam Hornacek /** 1464d8a7afe2SAdam Hornacek * Add a new file script to the page by the name. 1465d8a7afe2SAdam Hornacek * 1466d8a7afe2SAdam Hornacek * @param name name of the script to search for 1467d8a7afe2SAdam Hornacek * @return this 1468d8a7afe2SAdam Hornacek * 1469d8a7afe2SAdam Hornacek * @see Scripts#addScript(String, String, Scripts.Type) 1470d8a7afe2SAdam Hornacek */ 1471d8a7afe2SAdam Hornacek public PageConfig addScript(String name) { 1472d8a7afe2SAdam Hornacek this.scripts.addScript(this.req.getContextPath(), name, isDebug() ? Scripts.Type.DEBUG : Scripts.Type.MINIFIED); 1473d8a7afe2SAdam Hornacek return this; 1474d8a7afe2SAdam Hornacek } 1475d8a7afe2SAdam Hornacek 1476d8a7afe2SAdam Hornacek private boolean isDebug() { 1477d8a7afe2SAdam Hornacek return Boolean.parseBoolean(req.getParameter(DEBUG_PARAM_NAME)); 1478d8a7afe2SAdam Hornacek } 1479d8a7afe2SAdam Hornacek 1480d8a7afe2SAdam Hornacek /** 1481d8a7afe2SAdam Hornacek * Return the page scripts. 1482d8a7afe2SAdam Hornacek * 1483d8a7afe2SAdam Hornacek * @return the scripts 1484d8a7afe2SAdam Hornacek * 1485d8a7afe2SAdam Hornacek * @see Scripts 1486d8a7afe2SAdam Hornacek */ 1487d8a7afe2SAdam Hornacek public Scripts getScripts() { 1488d8a7afe2SAdam Hornacek return this.scripts; 1489d8a7afe2SAdam Hornacek } 1490d8a7afe2SAdam Hornacek 1491d8a7afe2SAdam Hornacek /** 1492d8a7afe2SAdam Hornacek * Get opengrok's configured data root directory. It is verified, that the 1493d8a7afe2SAdam Hornacek * used environment has a valid opengrok data root set and that it is an 1494d8a7afe2SAdam Hornacek * accessible directory. 1495d8a7afe2SAdam Hornacek * 1496d8a7afe2SAdam Hornacek * @return the opengrok data directory. 1497d8a7afe2SAdam Hornacek * @throws InvalidParameterException if inaccessible or not set. 1498d8a7afe2SAdam Hornacek */ 1499d8a7afe2SAdam Hornacek public File getDataRoot() { 1500d8a7afe2SAdam Hornacek if (dataRoot == null) { 1501d8a7afe2SAdam Hornacek String tmp = getEnv().getDataRootPath(); 1502d8a7afe2SAdam Hornacek if (tmp == null || tmp.length() == 0) { 1503d8a7afe2SAdam Hornacek throw new InvalidParameterException("dataRoot parameter is not " 1504d8a7afe2SAdam Hornacek + "set in configuration.xml!"); 1505d8a7afe2SAdam Hornacek } 1506d8a7afe2SAdam Hornacek dataRoot = new File(tmp); 1507d8a7afe2SAdam Hornacek if (!(dataRoot.isDirectory() && dataRoot.canRead())) { 1508d8a7afe2SAdam Hornacek throw new InvalidParameterException("The configured dataRoot '" 1509d8a7afe2SAdam Hornacek + tmp 1510d8a7afe2SAdam Hornacek + "' refers to a none-existing or unreadable directory!"); 1511d8a7afe2SAdam Hornacek } 1512d8a7afe2SAdam Hornacek } 1513d8a7afe2SAdam Hornacek return dataRoot; 1514d8a7afe2SAdam Hornacek } 1515d8a7afe2SAdam Hornacek 1516d8a7afe2SAdam Hornacek /** 15170516dd21SVladimir Kotal * Play nice in reverse proxy environment by using pre-configured hostname 15180516dd21SVladimir Kotal * request to construct the URLs. 15190516dd21SVladimir Kotal * Will not work well if the scheme or port is different for proxied server 15200516dd21SVladimir Kotal * and original server. 15210516dd21SVladimir Kotal * @return server name 15220516dd21SVladimir Kotal */ 15230516dd21SVladimir Kotal public String getServerName() { 15240516dd21SVladimir Kotal if (env.getServerName() != null) { 15250516dd21SVladimir Kotal return env.getServerName(); 15260516dd21SVladimir Kotal } else { 15270516dd21SVladimir Kotal return req.getServerName(); 15280516dd21SVladimir Kotal } 15290516dd21SVladimir Kotal } 15300516dd21SVladimir Kotal 15310516dd21SVladimir Kotal /** 1532d8a7afe2SAdam Hornacek * Prepare a search helper with all required information, ready to execute 1533d8a7afe2SAdam Hornacek * the query implied by the related request parameters and cookies. 1534d8a7afe2SAdam Hornacek * <p> 1535ae1c323bSVladimir Kotal * NOTE: One should check the {@link SearchHelper#getErrorMsg()} as well as 1536ae1c323bSVladimir Kotal * {@link SearchHelper#getRedirect()} and take the appropriate action before 1537d8a7afe2SAdam Hornacek * executing the prepared query or continue processing. 1538d8a7afe2SAdam Hornacek * <p> 1539d8a7afe2SAdam Hornacek * This method stops populating fields as soon as an error occurs. 1540d8a7afe2SAdam Hornacek * 1541d8a7afe2SAdam Hornacek * @return a search helper. 1542d8a7afe2SAdam Hornacek */ 1543d8a7afe2SAdam Hornacek public SearchHelper prepareSearch() { 1544d8a7afe2SAdam Hornacek List<SortOrder> sortOrders = getSortOrder(); 1545ae1c323bSVladimir Kotal SearchHelper sh = prepareInternalSearch(sortOrders.isEmpty() ? SortOrder.RELEVANCY : sortOrders.get(0)); 1546d8a7afe2SAdam Hornacek 1547d8a7afe2SAdam Hornacek if (getRequestedProjects().isEmpty() && getEnv().hasProjects()) { 1548ae1c323bSVladimir Kotal sh.setErrorMsg("You must select a project!"); 1549d8a7afe2SAdam Hornacek return sh; 1550d8a7afe2SAdam Hornacek } 1551d8a7afe2SAdam Hornacek 1552ae1c323bSVladimir Kotal if (sh.getBuilder().getSize() == 0) { 1553d8a7afe2SAdam Hornacek // Entry page show the map 1554ae1c323bSVladimir Kotal sh.setRedirect(req.getContextPath() + '/'); 1555d8a7afe2SAdam Hornacek return sh; 1556d8a7afe2SAdam Hornacek } 1557d8a7afe2SAdam Hornacek 1558d8a7afe2SAdam Hornacek return sh; 1559d8a7afe2SAdam Hornacek } 1560d8a7afe2SAdam Hornacek 1561d8a7afe2SAdam Hornacek /** 1562d8a7afe2SAdam Hornacek * Prepare a search helper with required settings for an internal search. 1563d8a7afe2SAdam Hornacek * <p> 1564ae1c323bSVladimir Kotal * NOTE: One should check the {@link SearchHelper#getErrorMsg()} as well as 1565ae1c323bSVladimir Kotal * {@link SearchHelper#getRedirect()} and take the appropriate action before 1566d8a7afe2SAdam Hornacek * executing the prepared query or continue processing. 1567d8a7afe2SAdam Hornacek * <p> 1568d8a7afe2SAdam Hornacek * This method stops populating fields as soon as an error occurs. 1569d8a7afe2SAdam Hornacek * @return a search helper. 1570d8a7afe2SAdam Hornacek */ 1571ae1c323bSVladimir Kotal public SearchHelper prepareInternalSearch(SortOrder sortOrder) { 1572d8a7afe2SAdam Hornacek String xrValue = req.getParameter(QueryParameters.NO_REDIRECT_PARAM); 1573ae1c323bSVladimir Kotal return new SearchHelper(getSearchStart(), sortOrder, getDataRoot(), new File(getSourceRootPath()), 1574ae1c323bSVladimir Kotal getSearchMaxItems(), getEftarReader(), getQueryBuilder(), getPrefix() == Prefix.SEARCH_R, 1575ae1c323bSVladimir Kotal req.getContextPath(), getPrefix() == Prefix.SEARCH_R || getPrefix() == Prefix.SEARCH_P, 1576ae1c323bSVladimir Kotal xrValue != null && !xrValue.isEmpty()); 1577d8a7afe2SAdam Hornacek } 1578d8a7afe2SAdam Hornacek 1579d8a7afe2SAdam Hornacek /** 1580d8a7afe2SAdam Hornacek * Get the config w.r.t. the given request. If there is none yet, a new config 1581d8a7afe2SAdam Hornacek * gets created, attached to the request and returned. 1582d8a7afe2SAdam Hornacek * <p> 1583d8a7afe2SAdam Hornacek * 1584d8a7afe2SAdam Hornacek * @param request the request to use to initialize the config parameters. 1585d8a7afe2SAdam Hornacek * @return always the same none-{@code null} config for a given request. 1586d8a7afe2SAdam Hornacek * @throws NullPointerException if the given parameter is {@code null}. 1587d8a7afe2SAdam Hornacek */ 1588d8a7afe2SAdam Hornacek public static PageConfig get(HttpServletRequest request) { 1589d8a7afe2SAdam Hornacek Object cfg = request.getAttribute(ATTR_NAME); 1590d8a7afe2SAdam Hornacek if (cfg != null) { 1591d8a7afe2SAdam Hornacek return (PageConfig) cfg; 1592d8a7afe2SAdam Hornacek } 1593d8a7afe2SAdam Hornacek PageConfig pcfg = new PageConfig(request); 1594d8a7afe2SAdam Hornacek request.setAttribute(ATTR_NAME, pcfg); 1595d8a7afe2SAdam Hornacek return pcfg; 1596d8a7afe2SAdam Hornacek } 1597d8a7afe2SAdam Hornacek 1598d8a7afe2SAdam Hornacek private PageConfig(HttpServletRequest req) { 1599d8a7afe2SAdam Hornacek this.req = req; 1600d8a7afe2SAdam Hornacek this.authFramework = RuntimeEnvironment.getInstance().getAuthorizationFramework(); 1601d8a7afe2SAdam Hornacek this.executor = RuntimeEnvironment.getInstance().getRevisionExecutor(); 1602d8a7afe2SAdam Hornacek this.fragmentIdentifier = req.getParameter(QueryParameters.FRAGMENT_IDENTIFIER_PARAM); 1603d8a7afe2SAdam Hornacek } 1604d8a7afe2SAdam Hornacek 1605d8a7afe2SAdam Hornacek /** 1606d8a7afe2SAdam Hornacek * Cleanup all allocated resources (if any) from the instance attached to 1607d8a7afe2SAdam Hornacek * the given request. 1608d8a7afe2SAdam Hornacek * 1609d8a7afe2SAdam Hornacek * @param sr request to check, cleanup. Ignored if {@code null}. 1610d8a7afe2SAdam Hornacek * @see PageConfig#get(HttpServletRequest) 1611d8a7afe2SAdam Hornacek */ 1612d8a7afe2SAdam Hornacek public static void cleanup(ServletRequest sr) { 1613d8a7afe2SAdam Hornacek if (sr == null) { 1614d8a7afe2SAdam Hornacek return; 1615d8a7afe2SAdam Hornacek } 1616d8a7afe2SAdam Hornacek PageConfig cfg = (PageConfig) sr.getAttribute(ATTR_NAME); 1617d8a7afe2SAdam Hornacek if (cfg == null) { 1618d8a7afe2SAdam Hornacek return; 1619d8a7afe2SAdam Hornacek } 1620d8a7afe2SAdam Hornacek ProjectHelper.cleanup(cfg); 1621d8a7afe2SAdam Hornacek sr.removeAttribute(ATTR_NAME); 1622d8a7afe2SAdam Hornacek cfg.env = null; 1623d8a7afe2SAdam Hornacek cfg.req = null; 1624d8a7afe2SAdam Hornacek if (cfg.eftarReader != null) { 1625d8a7afe2SAdam Hornacek cfg.eftarReader.close(); 1626d8a7afe2SAdam Hornacek } 1627d8a7afe2SAdam Hornacek } 1628d8a7afe2SAdam Hornacek 1629d8a7afe2SAdam Hornacek /** 1630d8a7afe2SAdam Hornacek * Checks if current request is allowed to access project. 1631d8a7afe2SAdam Hornacek * @param t project 1632d8a7afe2SAdam Hornacek * @return true if yes 1633d8a7afe2SAdam Hornacek */ 1634d8a7afe2SAdam Hornacek public boolean isAllowed(Project t) { 1635d8a7afe2SAdam Hornacek return this.authFramework.isAllowed(this.req, t); 1636d8a7afe2SAdam Hornacek } 1637d8a7afe2SAdam Hornacek 1638d8a7afe2SAdam Hornacek /** 1639d8a7afe2SAdam Hornacek * Checks if current request is allowed to access group. 1640d8a7afe2SAdam Hornacek * @param g group 1641d8a7afe2SAdam Hornacek * @return true if yes 1642d8a7afe2SAdam Hornacek */ 1643d8a7afe2SAdam Hornacek public boolean isAllowed(Group g) { 1644d8a7afe2SAdam Hornacek return this.authFramework.isAllowed(this.req, g); 1645d8a7afe2SAdam Hornacek } 1646d8a7afe2SAdam Hornacek 1647d8a7afe2SAdam Hornacek 1648d8a7afe2SAdam Hornacek public SortedSet<AcceptedMessage> getMessages() { 1649d8a7afe2SAdam Hornacek return env.getMessages(); 1650d8a7afe2SAdam Hornacek } 1651d8a7afe2SAdam Hornacek 1652d8a7afe2SAdam Hornacek public SortedSet<AcceptedMessage> getMessages(String tag) { 1653d8a7afe2SAdam Hornacek return env.getMessages(tag); 1654d8a7afe2SAdam Hornacek } 1655d8a7afe2SAdam Hornacek 1656d8a7afe2SAdam Hornacek /** 1657d8a7afe2SAdam Hornacek * Get basename of the path or "/" if the path is empty. 1658d8a7afe2SAdam Hornacek * This is used for setting title of various pages. 1659d8a7afe2SAdam Hornacek * @param path path 1660d8a7afe2SAdam Hornacek * @return short version of the path 1661d8a7afe2SAdam Hornacek */ 1662d8a7afe2SAdam Hornacek public String getShortPath(String path) { 1663d8a7afe2SAdam Hornacek File file = new File(path); 1664d8a7afe2SAdam Hornacek 1665d8a7afe2SAdam Hornacek if (path.isEmpty()) { 1666d8a7afe2SAdam Hornacek return "/"; 1667d8a7afe2SAdam Hornacek } 1668d8a7afe2SAdam Hornacek 1669d8a7afe2SAdam Hornacek return file.getName(); 1670d8a7afe2SAdam Hornacek } 1671d8a7afe2SAdam Hornacek 1672d8a7afe2SAdam Hornacek private String addTitleDelimiter(String title) { 1673d8a7afe2SAdam Hornacek if (!title.isEmpty()) { 1674d8a7afe2SAdam Hornacek return title + ", "; 1675d8a7afe2SAdam Hornacek } 1676d8a7afe2SAdam Hornacek 1677d8a7afe2SAdam Hornacek return title; 1678d8a7afe2SAdam Hornacek } 1679d8a7afe2SAdam Hornacek 1680d8a7afe2SAdam Hornacek /** 1681d8a7afe2SAdam Hornacek * The search page title string should progressively reflect the search terms 1682d8a7afe2SAdam Hornacek * so that if only small portion of the string is seen, it describes 1683d8a7afe2SAdam Hornacek * the action as closely as possible while remaining readable. 1684d8a7afe2SAdam Hornacek * @return string used for setting page title of search results page 1685d8a7afe2SAdam Hornacek */ 1686d8a7afe2SAdam Hornacek public String getSearchTitle() { 1687d8a7afe2SAdam Hornacek String title = ""; 1688d8a7afe2SAdam Hornacek 1689d8a7afe2SAdam Hornacek if (req.getParameter(QueryBuilder.FULL) != null && !req.getParameter(QueryBuilder.FULL).isEmpty()) { 1690d8a7afe2SAdam Hornacek title += req.getParameter(QueryBuilder.FULL) + " (full)"; 1691d8a7afe2SAdam Hornacek } 1692d8a7afe2SAdam Hornacek if (req.getParameter(QueryBuilder.DEFS) != null && !req.getParameter(QueryBuilder.DEFS).isEmpty()) { 1693d8a7afe2SAdam Hornacek title = addTitleDelimiter(title); 1694d8a7afe2SAdam Hornacek title += req.getParameter(QueryBuilder.DEFS) + " (definition)"; 1695d8a7afe2SAdam Hornacek } 1696d8a7afe2SAdam Hornacek if (req.getParameter(QueryBuilder.REFS) != null && !req.getParameter(QueryBuilder.REFS).isEmpty()) { 1697d8a7afe2SAdam Hornacek title = addTitleDelimiter(title); 1698d8a7afe2SAdam Hornacek title += req.getParameter(QueryBuilder.REFS) + " (reference)"; 1699d8a7afe2SAdam Hornacek } 1700d8a7afe2SAdam Hornacek if (req.getParameter(QueryBuilder.PATH) != null && !req.getParameter(QueryBuilder.PATH).isEmpty()) { 1701d8a7afe2SAdam Hornacek title = addTitleDelimiter(title); 1702d8a7afe2SAdam Hornacek title += req.getParameter(QueryBuilder.PATH) + " (path)"; 1703d8a7afe2SAdam Hornacek } 1704d8a7afe2SAdam Hornacek if (req.getParameter(QueryBuilder.HIST) != null && !req.getParameter(QueryBuilder.HIST).isEmpty()) { 1705d8a7afe2SAdam Hornacek title = addTitleDelimiter(title); 1706d8a7afe2SAdam Hornacek title += req.getParameter(QueryBuilder.HIST) + " (history)"; 1707d8a7afe2SAdam Hornacek } 1708d8a7afe2SAdam Hornacek 1709d8a7afe2SAdam Hornacek if (req.getParameterValues(QueryBuilder.PROJECT) != null && req.getParameterValues(QueryBuilder.PROJECT).length != 0) { 1710d8a7afe2SAdam Hornacek if (!title.isEmpty()) { 1711d8a7afe2SAdam Hornacek title += " "; 1712d8a7afe2SAdam Hornacek } 1713d8a7afe2SAdam Hornacek title += "in projects: "; 1714d8a7afe2SAdam Hornacek String[] projects = req.getParameterValues(QueryBuilder.PROJECT); 1715d8a7afe2SAdam Hornacek title += String.join(",", projects); 1716d8a7afe2SAdam Hornacek } 1717d8a7afe2SAdam Hornacek 1718d8a7afe2SAdam Hornacek return Util.htmlize(title + " - OpenGrok search results"); 1719d8a7afe2SAdam Hornacek } 1720d8a7afe2SAdam Hornacek 1721d8a7afe2SAdam Hornacek /** 1722d8a7afe2SAdam Hornacek * Similar as {@link #getSearchTitle()}. 1723d8a7afe2SAdam Hornacek * @return string used for setting page title of search view 1724d8a7afe2SAdam Hornacek */ 1725d8a7afe2SAdam Hornacek public String getHistoryTitle() { 1726d8a7afe2SAdam Hornacek String path = getPath(); 1727379f387bSVladimir Kotal return Util.htmlize(getShortPath(path) + " - OpenGrok history log for " + path); 1728d8a7afe2SAdam Hornacek } 1729d8a7afe2SAdam Hornacek 1730d8a7afe2SAdam Hornacek public String getPathTitle() { 1731d8a7afe2SAdam Hornacek String path = getPath(); 1732d8a7afe2SAdam Hornacek String title = getShortPath(path); 1733379f387bSVladimir Kotal if (!getRequestedRevision().isEmpty()) { 1734d8a7afe2SAdam Hornacek title += " (revision " + getRequestedRevision() + ")"; 1735d8a7afe2SAdam Hornacek } 1736d8a7afe2SAdam Hornacek title += " - OpenGrok cross reference for " + (path.isEmpty() ? "/" : path); 1737d8a7afe2SAdam Hornacek 1738d8a7afe2SAdam Hornacek return Util.htmlize(title); 1739d8a7afe2SAdam Hornacek } 1740d8a7afe2SAdam Hornacek 1741d8a7afe2SAdam Hornacek public void checkSourceRootExistence() throws IOException { 1742d8a7afe2SAdam Hornacek if (getSourceRootPath() == null || getSourceRootPath().isEmpty()) { 1743d8a7afe2SAdam Hornacek throw new FileNotFoundException("Unable to determine source root path. Missing configuration?"); 1744d8a7afe2SAdam Hornacek } 1745d8a7afe2SAdam Hornacek File sourceRootPathFile = RuntimeEnvironment.getInstance().getSourceRootFile(); 1746d8a7afe2SAdam Hornacek if (!sourceRootPathFile.exists()) { 1747379f387bSVladimir Kotal throw new FileNotFoundException(String.format("Source root path \"%s\" does not exist", 1748379f387bSVladimir Kotal sourceRootPathFile.getAbsolutePath())); 1749d8a7afe2SAdam Hornacek } 1750d8a7afe2SAdam Hornacek if (!sourceRootPathFile.isDirectory()) { 1751379f387bSVladimir Kotal throw new FileNotFoundException(String.format("Source root path \"%s\" is not a directory", 1752379f387bSVladimir Kotal sourceRootPathFile.getAbsolutePath())); 1753d8a7afe2SAdam Hornacek } 1754d8a7afe2SAdam Hornacek if (!sourceRootPathFile.canRead()) { 1755379f387bSVladimir Kotal throw new IOException(String.format("Source root path \"%s\" is not readable", 1756379f387bSVladimir Kotal sourceRootPathFile.getAbsolutePath())); 1757d8a7afe2SAdam Hornacek } 1758d8a7afe2SAdam Hornacek } 1759d8a7afe2SAdam Hornacek 1760d8a7afe2SAdam Hornacek /** 1761d8a7afe2SAdam Hornacek * Get all project related messages. These include 1762d8a7afe2SAdam Hornacek * <ol> 1763d8a7afe2SAdam Hornacek * <li>Main messages</li> 1764d8a7afe2SAdam Hornacek * <li>Messages with tag = project name</li> 1765d8a7afe2SAdam Hornacek * <li>Messages with tag = project's groups names</li> 1766d8a7afe2SAdam Hornacek * </ol> 1767d8a7afe2SAdam Hornacek * 1768379f387bSVladimir Kotal * @return the sorted set of messages according to accept time 1769d8a7afe2SAdam Hornacek * @see org.opengrok.indexer.web.messages.MessagesContainer#MESSAGES_MAIN_PAGE_TAG 1770d8a7afe2SAdam Hornacek */ 1771d8a7afe2SAdam Hornacek private SortedSet<AcceptedMessage> getProjectMessages() { 1772d8a7afe2SAdam Hornacek SortedSet<AcceptedMessage> messages = getMessages(); 1773d8a7afe2SAdam Hornacek 1774d8a7afe2SAdam Hornacek if (getProject() != null) { 1775d8a7afe2SAdam Hornacek messages.addAll(getMessages(getProject().getName())); 1776d8a7afe2SAdam Hornacek getProject().getGroups().forEach(group -> { 1777d8a7afe2SAdam Hornacek messages.addAll(getMessages(group.getName())); 1778d8a7afe2SAdam Hornacek }); 1779d8a7afe2SAdam Hornacek } 1780d8a7afe2SAdam Hornacek 1781d8a7afe2SAdam Hornacek return messages; 1782d8a7afe2SAdam Hornacek } 1783d8a7afe2SAdam Hornacek 1784d8a7afe2SAdam Hornacek /** 1785d8a7afe2SAdam Hornacek * Decide if this resource has been modified since the header value in the request. 1786d8a7afe2SAdam Hornacek * <p> 1787d8a7afe2SAdam Hornacek * The resource is modified since the weak ETag value in the request, the ETag is 1788d8a7afe2SAdam Hornacek * computed using: 17894a04c503SChris Fraire * 1790d8a7afe2SAdam Hornacek * <ul> 1791d8a7afe2SAdam Hornacek * <li>the source file modification</li> 1792d8a7afe2SAdam Hornacek * <li>project messages</li> 1793d8a7afe2SAdam Hornacek * <li>last timestamp for index</li> 1794d8a7afe2SAdam Hornacek * <li>OpenGrok current deployed version</li> 1795d8a7afe2SAdam Hornacek * </ul> 1796d8a7afe2SAdam Hornacek * 1797d8a7afe2SAdam Hornacek * <p> 1798d8a7afe2SAdam Hornacek * If the resource was modified, appropriate headers in the response are filled. 17994a04c503SChris Fraire * 1800d8a7afe2SAdam Hornacek * 1801d8a7afe2SAdam Hornacek * @param request the http request containing the headers 1802d8a7afe2SAdam Hornacek * @param response the http response for setting the headers 1803d8a7afe2SAdam Hornacek * @return true if resource was not modified; false otherwise 1804d8a7afe2SAdam Hornacek * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">HTTP ETag</a> 1805d8a7afe2SAdam Hornacek * @see <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html">HTTP Caching</a> 1806d8a7afe2SAdam Hornacek */ 1807d8a7afe2SAdam Hornacek public boolean isNotModified(HttpServletRequest request, HttpServletResponse response) { 1808d8a7afe2SAdam Hornacek String currentEtag = String.format("W/\"%s\"", 1809d8a7afe2SAdam Hornacek Objects.hash( 1810d8a7afe2SAdam Hornacek // last modified time as UTC timestamp in millis 1811d8a7afe2SAdam Hornacek getLastModified(), 1812d8a7afe2SAdam Hornacek // all project related messages which changes the view 1813d8a7afe2SAdam Hornacek getProjectMessages(), 1814d8a7afe2SAdam Hornacek // last timestamp value 1815d8a7afe2SAdam Hornacek getEnv().getDateForLastIndexRun() != null ? getEnv().getDateForLastIndexRun().getTime() : 0, 1816d8a7afe2SAdam Hornacek // OpenGrok version has changed since the last time 1817d8a7afe2SAdam Hornacek Info.getVersion() 1818d8a7afe2SAdam Hornacek ) 1819d8a7afe2SAdam Hornacek ); 1820d8a7afe2SAdam Hornacek 1821d8a7afe2SAdam Hornacek String headerEtag = request.getHeader(HttpHeaders.IF_NONE_MATCH); 1822d8a7afe2SAdam Hornacek 1823d8a7afe2SAdam Hornacek if (headerEtag != null && headerEtag.equals(currentEtag)) { 1824d8a7afe2SAdam Hornacek // weak ETag has not changed, return 304 NOT MODIFIED 1825d8a7afe2SAdam Hornacek response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); 1826d8a7afe2SAdam Hornacek return true; 1827d8a7afe2SAdam Hornacek } 1828d8a7afe2SAdam Hornacek 1829d8a7afe2SAdam Hornacek // return 200 OK 1830d8a7afe2SAdam Hornacek response.setHeader(HttpHeaders.ETAG, currentEtag); 1831d8a7afe2SAdam Hornacek return false; 1832d8a7afe2SAdam Hornacek } 1833d8a7afe2SAdam Hornacek 1834d8a7afe2SAdam Hornacek /** 1835d8a7afe2SAdam Hornacek * @param root root path 1836d8a7afe2SAdam Hornacek * @param path path 1837d8a7afe2SAdam Hornacek * @return path relative to root 1838d8a7afe2SAdam Hornacek */ 1839d8a7afe2SAdam Hornacek public static String getRelativePath(String root, String path) { 1840d8a7afe2SAdam Hornacek return Paths.get(root).relativize(Paths.get(path)).toString(); 1841d8a7afe2SAdam Hornacek } 1842d8a7afe2SAdam Hornacek 1843d8a7afe2SAdam Hornacek /** 1844d8a7afe2SAdam Hornacek * Determines whether a match offset from a search result has been 1845d8a7afe2SAdam Hornacek * indicated, and if so tries to calculate a translated xref fragment 1846d8a7afe2SAdam Hornacek * identifier. 1847379f387bSVladimir Kotal * @return {@code true} if a xref fragment identifier was calculated by the call to this method 1848d8a7afe2SAdam Hornacek */ 1849d8a7afe2SAdam Hornacek public boolean evaluateMatchOffset() { 1850d8a7afe2SAdam Hornacek if (fragmentIdentifier == null) { 1851d8a7afe2SAdam Hornacek int matchOffset = getIntParam(QueryParameters.MATCH_OFFSET_PARAM, -1); 1852d8a7afe2SAdam Hornacek if (matchOffset >= 0) { 1853d8a7afe2SAdam Hornacek File resourceFile = getResourceFile(); 1854d8a7afe2SAdam Hornacek if (resourceFile.isFile()) { 1855d8a7afe2SAdam Hornacek LineBreaker breaker = new LineBreaker(); 1856d8a7afe2SAdam Hornacek StreamSource streamSource = StreamSource.fromFile(resourceFile); 1857d8a7afe2SAdam Hornacek try { 1858d8a7afe2SAdam Hornacek breaker.reset(streamSource, in -> ExpandTabsReader.wrap(in, getProject())); 1859d8a7afe2SAdam Hornacek int matchLine = breaker.findLineIndex(matchOffset); 1860d8a7afe2SAdam Hornacek if (matchLine >= 0) { 1861d8a7afe2SAdam Hornacek // Convert to 1-based offset to accord with OpenGrok line number. 1862d8a7afe2SAdam Hornacek fragmentIdentifier = String.valueOf(matchLine + 1); 1863d8a7afe2SAdam Hornacek return true; 1864d8a7afe2SAdam Hornacek } 1865d8a7afe2SAdam Hornacek } catch (IOException e) { 1866379f387bSVladimir Kotal LOGGER.log(Level.WARNING, String.format("Failed to evaluate match offset for %s", 1867379f387bSVladimir Kotal resourceFile), e); 1868d8a7afe2SAdam Hornacek } 1869d8a7afe2SAdam Hornacek } 1870d8a7afe2SAdam Hornacek } 1871d8a7afe2SAdam Hornacek } 1872d8a7afe2SAdam Hornacek return false; 1873d8a7afe2SAdam Hornacek } 1874d8a7afe2SAdam Hornacek } 1875