PageConfig.java revision 1211
1185N/A/*
1185N/A * CDDL HEADER START
1185N/A *
1185N/A * The contents of this file are subject to the terms of the
1185N/A * Common Development and Distribution License (the "License").
1185N/A * You may not use this file except in compliance with the License.
1185N/A *
1185N/A * See LICENSE.txt included in this distribution for the specific
1185N/A * language governing permissions and limitations under the License.
1185N/A *
1185N/A * When distributing Covered Code, include this CDDL HEADER in each
1185N/A * file and include the License file at LICENSE.txt.
1185N/A * If applicable, add the following below this CDDL HEADER, with the
1185N/A * fields enclosed by brackets "[]" replaced with your own identifying
1185N/A * information: Portions Copyright [yyyy] [name of copyright owner]
1185N/A *
1185N/A * CDDL HEADER END
1185N/A */
1185N/A/*
1185N/A * Copyright (c) 2011 Jens Elkner.
1185N/A */
1185N/Apackage org.opensolaris.opengrok.web;
1185N/A
1185N/Aimport java.io.BufferedReader;
1185N/Aimport java.io.File;
1185N/Aimport java.io.IOException;
1185N/Aimport java.io.InputStream;
1185N/Aimport java.io.InputStreamReader;
1185N/Aimport java.net.URI;
1185N/Aimport java.net.URISyntaxException;
1185N/Aimport java.security.InvalidParameterException;
1185N/Aimport java.util.ArrayList;
1185N/Aimport java.util.EnumSet;
1185N/Aimport java.util.List;
1198N/Aimport java.util.Set;
1185N/Aimport java.util.TreeSet;
1194N/Aimport java.util.logging.Level;
1194N/Aimport java.util.logging.Logger;
1185N/Aimport java.util.regex.Pattern;
1185N/A
1185N/Aimport javax.servlet.http.Cookie;
1185N/Aimport javax.servlet.http.HttpServletRequest;
1185N/A
1185N/Aimport org.apache.commons.jrcs.diff.Diff;
1185N/Aimport org.apache.commons.jrcs.diff.DifferentiationFailedException;
1185N/Aimport org.opensolaris.opengrok.analysis.AnalyzerGuru;
1185N/Aimport org.opensolaris.opengrok.analysis.ExpandTabsReader;
1185N/Aimport org.opensolaris.opengrok.analysis.FileAnalyzer.Genre;
1185N/Aimport org.opensolaris.opengrok.configuration.Project;
1185N/Aimport org.opensolaris.opengrok.configuration.RuntimeEnvironment;
1185N/Aimport org.opensolaris.opengrok.history.Annotation;
1185N/Aimport org.opensolaris.opengrok.history.HistoryGuru;
1185N/Aimport org.opensolaris.opengrok.index.IgnoredNames;
1185N/Aimport org.opensolaris.opengrok.search.QueryBuilder;
1194N/Aimport org.opensolaris.opengrok.util.IOUtils;
1185N/A
1185N/A/**
1190N/A * A simple container to lazy initialize common vars wrt. a single request.
1190N/A * It MUST NOT be shared between several requests and {@link #cleanup()} should
1185N/A * be called before the page context gets destroyed (e.g. by overwriting
1190N/A * {@code jspDestroy()} or when leaving the {@code service} method.
1185N/A * <p>
1190N/A * Purpose is to decouple implementation details from web design, so that the
1190N/A * JSP developer does not need to know every implementation detail and normally
1190N/A * has to deal with this class/wrapper, only (so some people may like to call
1185N/A * this class a bean with request scope ;-)). Furthermore it helps to keep the
1190N/A * pages (how content gets generated) consistent and to document the request
1185N/A * parameters used.
1185N/A * <p>
1185N/A * General contract for this class (i.e. if not explicitly documented):
1185N/A * no method of this class changes neither the request nor the response.
1190N/A *
1185N/A * @author Jens Elkner
1185N/A * @version $Revision$
1185N/A */
1210N/Apublic final class PageConfig {
1185N/A // TODO if still used, get it from the app context
1185N/A
1185N/A boolean check4on = true;
1185N/A private RuntimeEnvironment env;
1185N/A private IgnoredNames ignoredNames;
1185N/A private String path;
1185N/A private File resourceFile;
1185N/A private String resourcePath;
1185N/A private EftarFileReader eftarReader;
1185N/A private String sourceRootPath;
1185N/A private Boolean isDir;
1185N/A private String uriEncodedPath;
1185N/A private Prefix prefix;
1185N/A private String pageTitle;
1185N/A private String dtag;
1185N/A private String rev;
1185N/A private Boolean hasAnnotation;
1185N/A private Boolean annotate;
1185N/A private Annotation annotation;
1185N/A private Boolean hasHistory;
1185N/A private static final EnumSet<Genre> txtGenres =
1185N/A EnumSet.of(Genre.DATA, Genre.PLAIN, Genre.HTML);
1198N/A private Set<String> requestedProjects;
1185N/A private String requestedProjectsString;
1185N/A private String[] dirFileList;
1185N/A private QueryBuilder queryBuilder;
1185N/A private File dataRoot;
1185N/A private StringBuilder headLines;
1211N/A private static final Logger log = Logger.getLogger(PageConfig.class.getName());
1211N/A
1185N/A /**
1190N/A * Add the given data to the &lt;head&gt; section of the html page to
1185N/A * generate.
1190N/A * @param data data to add. It is copied as is, so remember to escape
1185N/A * special characters ...
1185N/A */
1185N/A public void addHeaderData(String data) {
1185N/A if (data == null || data.length() == 0) {
1185N/A return;
1185N/A }
1185N/A if (headLines == null) {
1185N/A headLines = new StringBuilder();
1185N/A }
1185N/A headLines.append(data);
1185N/A }
1185N/A
1185N/A /**
1190N/A * Get addition data, which should be added as is to the &lt;head&gt;
1185N/A * section of the html page.
1185N/A * @return an empty string if nothing to add, the data otherwise.
1185N/A */
1185N/A public String getHeaderData() {
1185N/A return headLines == null ? "" : headLines.toString();
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get all data required to create a diff view wrt. to this request in one go.
1185N/A * @return an instance with just enough information to render a sufficient
1190N/A * view. If not all required parameters were given either they are
1190N/A * supplemented with reasonable defaults if possible, otherwise the
1190N/A * related field(s) are {@code null}. {@link DiffData#errorMsg}
1185N/A * {@code != null} indicates, that an error occured and one should not
1185N/A * try to render a view.
1185N/A */
1185N/A public DiffData getDiffData() {
1185N/A DiffData data = new DiffData();
1185N/A data.path = getPath().substring(0, path.lastIndexOf('/'));
1185N/A data.filename = Util.htmlize(getResourceFile().getName());
1185N/A
1185N/A String srcRoot = getSourceRootPath();
1185N/A String context = req.getContextPath();
1185N/A
1185N/A String[] path = new String[2];
1185N/A data.rev = new String[2];
1185N/A data.file = new String[2][];
1185N/A data.param = new String[2];
1185N/A for (int i = 1; i <= 2; i++) {
1185N/A String[] tmp = null;
1185N/A String p = req.getParameter("r" + i);
1185N/A if (p != null) {
1185N/A tmp = p.split("@");
1185N/A }
1185N/A if (tmp != null && tmp.length == 2) {
1185N/A path[i - 1] = tmp[0];
1185N/A data.rev[i - 1] = tmp[1];
1185N/A }
1185N/A }
1185N/A if (data.rev[0] == null || data.rev[1] == null
1185N/A || data.rev[0].length() == 0 || data.rev[1].length() == 0
1185N/A || data.rev[0].equals(data.rev[1])) {
1185N/A data.errorMsg = "Please pick two revisions to compare the changed "
1185N/A + "from the <a href=\"" + context + Prefix.HIST_L
1185N/A + getUriEncodedPath() + "\">history</a>";
1185N/A return data;
1185N/A }
1185N/A data.genre = AnalyzerGuru.getGenre(getResourceFile().getName());
1185N/A if (data.genre == Genre.IMAGE) {
1185N/A return data; // no more info needed
1185N/A }
1185N/A if (data.genre == null || txtGenres.contains(data.genre)) {
1185N/A InputStream[] in = new InputStream[2];
1185N/A BufferedReader br = null;
1185N/A try {
1185N/A for (int i = 0; i < 2; i++) {
1185N/A File f = new File(srcRoot + path[i]);
1185N/A in[i] = HistoryGuru.getInstance().getRevision(f.getParent(), f.getName(), data.rev[i]);
1185N/A }
1185N/A if (data.genre == null) {
1185N/A try {
1185N/A data.genre = AnalyzerGuru.getGenre(in[0]);
1185N/A } catch (IOException e) {
1185N/A data.errorMsg = "Unable to determine the file type: "
1185N/A + Util.htmlize(e.getMessage());
1185N/A }
1185N/A if (data.genre == Genre.IMAGE
1185N/A || (data.genre != Genre.PLAIN
1185N/A && data.genre != Genre.HTML)) {
1185N/A return data;
1185N/A }
1185N/A }
1185N/A ArrayList<String> lines = new ArrayList<String>();
1185N/A Project p = getProject();
1185N/A for (int i = 0; i < 2; i++) {
1185N/A br = new BufferedReader(ExpandTabsReader.wrap(new InputStreamReader(in[i]), p));
1185N/A String line;
1185N/A while ((line = br.readLine()) != null) {
1185N/A lines.add(line);
1185N/A }
1185N/A data.file[i] = lines.toArray(new String[lines.size()]);
1185N/A lines.clear();
1195N/A IOUtils.close(br);
1185N/A in[i] = null;
1185N/A br = null;
1185N/A }
1185N/A } catch (Exception e) {
1185N/A data.errorMsg = "Error reading revisions: "
1185N/A + Util.htmlize(e.getMessage());
1185N/A } finally {
1194N/A IOUtils.close(br);
1185N/A for (int i = 0; i < 2; i++) {
1194N/A IOUtils.close(in[i]);
1185N/A }
1185N/A }
1185N/A if (data.errorMsg != null) {
1185N/A return data;
1185N/A }
1185N/A try {
1185N/A data.revision = Diff.diff(data.file[0], data.file[1]);
1185N/A } catch (DifferentiationFailedException e) {
1185N/A data.errorMsg = "Unable to get diffs: "
1185N/A + Util.htmlize(e.getMessage());
1185N/A }
1185N/A for (int i = 0; i < 2; i++) {
1185N/A try {
1185N/A URI u = new URI(null, null, null,
1185N/A path[i] + "@" + data.rev[i], null);
1185N/A data.param[i] = u.getRawQuery();
1185N/A } catch (URISyntaxException e) {
1211N/A log.log(Level.WARNING, "Failed to create URI: ", e);
1185N/A }
1185N/A }
1185N/A data.full = fullDiff();
1185N/A data.type = getDiffType();
1185N/A }
1185N/A return data;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the diff display type to use wrt. the request parameter {@code format}.
1185N/A * @return {@link DiffType#SIDEBYSIDE} if the request contains no such parameter
1185N/A * or one with an unknown value, the recognized diff type otherwise.
1185N/A * @see DiffType#get(String)
1185N/A * @see DiffType#getAbbrev()
1185N/A * @see DiffType#toString()
1185N/A */
1185N/A public DiffType getDiffType() {
1185N/A DiffType d = DiffType.get(req.getParameter("format"));
1185N/A return d == null ? DiffType.SIDEBYSIDE : d;
1185N/A }
1185N/A
1185N/A /**
1190N/A * Check, whether a full diff should be displayed.
1190N/A * @return {@code true} if a request parameter {@code full} with the
1190N/A * literal value {@code 1} was found.
1185N/A */
1185N/A public boolean fullDiff() {
1185N/A String val = req.getParameter("full");
1185N/A return val != null && val.equals("1");
1185N/A }
1185N/A
1185N/A /**
1185N/A * Check, whether the request contains minial information required to
1185N/A * produce a valid page. If this method returns an empty string, the
1190N/A * referred file or directory actually exists below the source root
1185N/A * directory and is readable.
1190N/A *
1190N/A * @return {@code null} if the referred src file, directory or history is not
1185N/A * available, an empty String if further processing is ok and a none-empty
1190N/A * string which contains the URI encoded redirect path if the request
1185N/A * should be redirected.
1185N/A * @see #resourceNotAvailable()
1185N/A * @see #getOnRedirect()
1185N/A * @see #getDirectoryRedirect()
1185N/A */
1185N/A public String canProcess() {
1185N/A if (resourceNotAvailable()) {
1185N/A return getOnRedirect();
1185N/A }
1185N/A String redir = getDirectoryRedirect();
1185N/A if (redir == null && getPrefix() == Prefix.HIST_L && !hasHistory()) {
1185N/A return null;
1185N/A }
1185N/A // jel: outfactored from list.jsp - seems to be bogus
1185N/A if (isDir()) {
1185N/A if (getPrefix() == Prefix.XREF_P) {
1185N/A String[] list = getResourceFileList();
1185N/A if (list.length == 0) {
1185N/A String rev = getRequestedRevision();
1185N/A if (rev.length() != 0 && !hasHistory()) {
1185N/A return null;
1185N/A }
1185N/A }
1185N/A } else if (getPrefix() == Prefix.RAW_P) {
1185N/A return null;
1185N/A }
1185N/A }
1185N/A return redir == null ? "" : redir;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get a list of filenames in the requested path.
1190N/A * @return an empty array, if the resource does not exist, is not a
1185N/A * directory or an error occured when reading it, otherwise a list of
1185N/A * filenames in that directory.
1185N/A * @see #getResourceFile()
1185N/A * @see #isDir()
1185N/A */
1185N/A public String[] getResourceFileList() {
1185N/A if (dirFileList == null) {
1185N/A if (isDir() && getResourcePath().length() > 1) {
1185N/A dirFileList = getResourceFile().list();
1185N/A }
1185N/A if (dirFileList == null) {
1185N/A dirFileList = new String[0];
1185N/A }
1185N/A }
1185N/A return dirFileList;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the time of last modification of the related file or directory.
1185N/A * @return the last modification time of the related file or directory.
1185N/A * @see File#lastModified()
1185N/A */
1185N/A public long getLastModified() {
1185N/A return getResourceFile().lastModified();
1185N/A }
1185N/A
1185N/A /**
1190N/A * Get all RSS related directories from the request using its {@code also}
1185N/A * parameter.
1185N/A * @return an empty string if the requested resource is not a directory, a
1185N/A * space (' ') separated list of unchecked directory names otherwise.
1185N/A */
1185N/A public String getHistoryDirs() {
1185N/A if (!isDir()) {
1185N/A return "";
1185N/A }
1185N/A String[] val = req.getParameterValues("also");
1185N/A if (val == null || val.length == 0) {
1185N/A return path;
1185N/A }
1185N/A StringBuilder paths = new StringBuilder(path);
1185N/A for (int i = 0; i < val.length; i++) {
1185N/A paths.append(' ').append(val[i]);
1185N/A }
1185N/A return paths.toString();
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the int value of the given request parameter.
1185N/A * @param name name of the parameter to lookup.
1185N/A * @param defaultValue value to return, if the parameter is not set, is not
1185N/A * a number, or is &lt; 0.
1185N/A * @return the parsed int value on success, the given default value otherwise.
1185N/A */
1185N/A public int getIntParam(String name, int defaultValue) {
1210N/A int ret = defaultValue;
1185N/A String s = req.getParameter(name);
1185N/A if (s != null && s.length() != 0) {
1185N/A try {
1185N/A int x = Integer.parseInt(s, 10);
1185N/A if (x >= 0) {
1210N/A ret = x;
1185N/A }
1185N/A } catch (Exception e) {
1211N/A log.log(Level.INFO, "Failed to parse integer " + s, e);
1185N/A }
1185N/A }
1210N/A return ret;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the <b>start</b> index for a search result to return by looking up
1185N/A * the {@code start} request parameter.
1185N/A * @return 0 if the corresponding start parameter is not set or not a number,
1185N/A * the number found otherwise.
1185N/A */
1185N/A public int getSearchStart() {
1185N/A return getIntParam("start", 0);
1185N/A }
1185N/A
1185N/A /**
1190N/A * Get the number of search results to max. return by looking up the
1185N/A * {@code n} request parameter.
1190N/A *
1190N/A * @return the default number of hits if the corresponding start parameter
1185N/A * is not set or not a number, the number found otherwise.
1185N/A */
1185N/A public int getSearchMaxItems() {
1185N/A return getIntParam("n", getEnv().getHitsPerPage());
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get sort orders from the request parameter {@code sort} and if this list
1185N/A * would be empty from the cookie {@code OpenGrokorting}.
1190N/A * @return a possible empty list which contains the sort order values in
1185N/A * the same order supplied by the request parameter or cookie(s).
1185N/A */
1185N/A public List<SortOrder> getSortOrder() {
1185N/A List<SortOrder> sort = new ArrayList<SortOrder>();
1198N/A List<String> vals = getParamVals("sort");
1185N/A for (String s : vals) {
1185N/A SortOrder so = SortOrder.get(s);
1185N/A if (so != null) {
1185N/A sort.add(so);
1185N/A }
1185N/A }
1185N/A if (sort.isEmpty()) {
1185N/A vals = getCookieVals("OpenGrokSorting");
1185N/A for (String s : vals) {
1185N/A SortOrder so = SortOrder.get(s);
1185N/A if (so != null) {
1185N/A sort.add(so);
1185N/A }
1185N/A }
1185N/A }
1185N/A return sort;
1185N/A }
1185N/A
1185N/A /**
1190N/A * Get a reference to the {@code QueryBuilder} wrt. to the current request
1185N/A * parameters:
1185N/A * <dl>
1185N/A * <dt>q</dt>
1185N/A * <dd>freetext lookup rules</dd>
1185N/A * <dt>defs</dt>
1185N/A * <dd>definitions lookup rules</dd>
1185N/A * <dt>path</dt>
1185N/A * <dd>path related rules</dd>
1185N/A * <dt>hist</dt>
1185N/A * <dd>history related rules</dd>
1185N/A * </dl>
1185N/A * @return a query builder with all relevant fields populated.
1185N/A */
1185N/A public QueryBuilder getQueryBuilder() {
1185N/A if (queryBuilder == null) {
1185N/A queryBuilder = new QueryBuilder().setFreetext(req.getParameter("q")).setDefs(req.getParameter("defs")).setRefs(req.getParameter("refs")).setPath(req.getParameter("path")).setHist(req.getParameter("hist"));
1185N/A
1185N/A // This is for backward compatibility with links created by OpenGrok
1190N/A // 0.8.x and earlier. We used to concatenate the entire query into a
1190N/A // single string and send it in the t parameter. If we get such a
1190N/A // link, just add it to the freetext field, and we'll get the old
1185N/A // behaviour. We can probably remove this code in the first feature
1185N/A // release after 0.9.
1185N/A String t = req.getParameter("t");
1185N/A if (t != null) {
1185N/A queryBuilder.setFreetext(t);
1185N/A }
1185N/A }
1185N/A return queryBuilder;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the eftar reader for the opengrok data directory. If it has been
1185N/A * already opened and not closed, this instance gets returned. One should
1185N/A * not close it once used: {@link #cleanup()} takes care to close it.
1190N/A *
1190N/A * @return {@code null} if a reader can't be established, the reader
1185N/A * otherwise.
1185N/A */
1185N/A public EftarFileReader getEftarReader() {
1185N/A if (eftarReader == null || eftarReader.isClosed()) {
1185N/A try {
1185N/A eftarReader = new EftarFileReader(getEnv().getDataRootPath()
1185N/A + "/index/dtags.eftar");
1185N/A } catch (Exception e) {
1211N/A log.log(Level.INFO, "Failed to create EftarFileReader: ", e);
1185N/A }
1185N/A }
1185N/A return eftarReader;
1185N/A }
1185N/A
1185N/A /**
1190N/A * Get the definition tag for the request related file or directory.
1185N/A * @return an empty string if not found, the tag otherwise.
1185N/A */
1185N/A public String getDefineTagsIndex() {
1185N/A if (dtag != null) {
1185N/A return dtag;
1185N/A }
1185N/A getEftarReader();
1185N/A if (eftarReader != null) {
1185N/A try {
1185N/A dtag = eftarReader.get(getPath());
1185N/A // cfg.getPrefix() != Prefix.XREF_S) {
1185N/A } catch (IOException e) {
1211N/A log.log(Level.INFO, "Failed to get entry from eftar reader: ", e);
1185N/A }
1185N/A }
1185N/A if (dtag == null) {
1185N/A dtag = "";
1185N/A }
1185N/A return dtag;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the revision parameter {@code r} from the request.
1185N/A * @return {@code "r=<i>revision</i>"} if found, an empty string otherwise.
1185N/A */
1185N/A public String getRequestedRevision() {
1185N/A if (rev == null) {
1185N/A String tmp = req.getParameter("r");
1185N/A rev = (tmp != null && tmp.length() > 0) ? "r=" + tmp : "";
1185N/A }
1185N/A return rev;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Check, whether the request related resource has history information.
1185N/A * @return {@code true} if history is available.
1185N/A * @see HistoryGuru#hasHistory(File)
1185N/A */
1185N/A public boolean hasHistory() {
1185N/A if (hasHistory == null) {
1185N/A hasHistory = Boolean.valueOf(HistoryGuru.getInstance().hasHistory(getResourceFile()));
1185N/A }
1185N/A return hasHistory.booleanValue();
1185N/A }
1185N/A
1185N/A /**
1185N/A * Check, whether annotations are available for the related resource.
1185N/A * @return {@code true} if annotions are available.
1185N/A */
1185N/A public boolean hasAnnotations() {
1185N/A if (hasAnnotation == null) {
1185N/A hasAnnotation = Boolean.valueOf(!isDir()
1185N/A && HistoryGuru.getInstance().hasHistory(getResourceFile()));
1185N/A }
1185N/A return hasAnnotation.booleanValue();
1185N/A }
1185N/A
1185N/A /**
1185N/A * Check, whether the resource to show should be annotated.
1185N/A * @return {@code true} if annotation is desired and available.
1185N/A */
1185N/A public boolean annotate() {
1185N/A if (annotate == null) {
1185N/A annotate = Boolean.valueOf(hasAnnotations()
1185N/A && Boolean.parseBoolean(req.getParameter("a")));
1185N/A }
1185N/A return annotate.booleanValue();
1185N/A }
1185N/A
1185N/A /**
1190N/A * Get the annotation for the reqested resource.
1190N/A * @return {@code null} if not available or annotation was not requested,
1185N/A * the cached annotation otherwise.
1185N/A */
1185N/A public Annotation getAnnotation() {
1185N/A if (isDir() || getResourcePath().equals("/") || !annotate()) {
1185N/A return null;
1185N/A }
1185N/A if (annotation != null) {
1185N/A return annotation;
1185N/A }
1185N/A getRequestedRevision();
1185N/A try {
1185N/A annotation = HistoryGuru.getInstance().annotate(resourceFile, rev.isEmpty() ? null : rev.substring(2));
1185N/A } catch (IOException e) {
1185N/A /* ignore */
1185N/A }
1185N/A return annotation;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the name which should be show as "Crossfile"
1185N/A * @return the name of the related file or directory.
1185N/A */
1185N/A public String getCrossFilename() {
1185N/A return getResourceFile().getName();
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the {@code path} parameter and display value for "Search only in"
1190N/A * option.
1185N/A * @return always an array of 3 fields, whereby field[0] contains the
1190N/A * path value to use (starts and ends always with a '/'). Field[1] the
1190N/A * contains string to show in the UI. field[2] is set to
1190N/A * {@code disabled=""} if the current path is the "/" directory,
1185N/A * otherwise set to an empty string.
1185N/A */
1185N/A public String[] getSearchOnlyIn() {
1185N/A if (isDir()) {
1185N/A return path.length() == 0
1185N/A ? new String[]{"/", "/", "disabled=\"\""}
1185N/A : new String[]{path, path, ""};
1185N/A }
1185N/A String[] res = new String[3];
1185N/A res[0] = path.substring(0, path.lastIndexOf('/') + 1);
1185N/A res[1] = path.substring(res[0].length());
1185N/A res[2] = "";
1185N/A return res;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the project {@link #getPath()} refers to.
1185N/A * @return {@code null} if not available, the project otherwise.
1185N/A */
1185N/A public Project getProject() {
1185N/A return Project.getProject(getResourceFile());
1185N/A }
1185N/A
1185N/A /**
1185N/A * Same as {@link #getRequestedProjects()} but returns the project names as
1185N/A * a coma separated String.
1185N/A * @return a possible empty String but never {@code null}.
1185N/A */
1185N/A public String getRequestedProjectsAsString() {
1185N/A if (requestedProjectsString == null) {
1198N/A Set<String> projects = getRequestedProjects();
1185N/A if (projects.isEmpty()) {
1185N/A requestedProjectsString = "";
1185N/A } else {
1185N/A StringBuilder buf = new StringBuilder();
1185N/A for (String name : projects) {
1185N/A buf.append(name).append(',');
1185N/A }
1185N/A buf.setLength(buf.length() - 1);
1185N/A requestedProjectsString = buf.toString();
1185N/A }
1185N/A }
1185N/A return requestedProjectsString;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the document hash provided by the request parameter {@code h}.
1190N/A * @return {@code null} if the request does not contain such a parameter,
1185N/A * its value otherwise.
1185N/A */
1185N/A public String getDocumentHash() {
1185N/A return req.getParameter("h");
1185N/A }
1185N/A
1185N/A /**
1190N/A * Get a reference to a set of requested projects via request parameter
1185N/A * {@code project} or cookies or defaults.
1185N/A * <p>
1185N/A * NOTE: This method assumes, that project names do <b>not</b> contain
1185N/A * a comma (','), since this character is used as name separator!
1190N/A *
1190N/A * @return a possible empty set of project names aka descriptions but never
1190N/A * {@code null}. It is determined as
1185N/A * follows:
1185N/A * <ol>
1185N/A * <li>If there is no project in the runtime environment (RTE) an empty
1185N/A * set is returned. Otherwise:</li>
1190N/A * <li>If there is only one project in the RTE, this one gets returned (no
1190N/A * matter, what the request actually says). Otherwise</li>
1185N/A * <li>If the request parameter {@code project} contains any available
1185N/A * project, the set with invalid projects removed gets returned.
1185N/A * Otherwise:</li>
1190N/A * <li>If the request has a cookie with the name {@code OpenGrokProject}
1190N/A * and it contains any available project, the set with invalid
1185N/A * projects removed gets returned. Otherwise:</li>
1185N/A * <li>If a default project is set in the RTE, this project gets returned.
1185N/A * Otherwise:</li>
1185N/A * <li>an empty set</li>
1185N/A * </ol>
1185N/A */
1198N/A public Set<String> getRequestedProjects() {
1185N/A if (requestedProjects == null) {
1185N/A requestedProjects =
1185N/A getRequestedProjects("project", "OpenGrokProject");
1185N/A }
1185N/A return requestedProjects;
1185N/A }
1185N/A private static Pattern COMMA_PATTERN = Pattern.compile("'");
1185N/A
1185N/A private static final void splitByComma(String value, List<String> result) {
1185N/A if (value == null || value.length() == 0) {
1185N/A return;
1185N/A }
1185N/A String p[] = COMMA_PATTERN.split(value);
1185N/A for (int k = 0; k < p.length; k++) {
1185N/A if (p[k].length() != 0) {
1185N/A result.add(p[k]);
1185N/A }
1185N/A }
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the cookie values for the given name. Splits comma separated values
1185N/A * automatically into a list of Strings.
1185N/A * @param cookieName name of the cookie.
1185N/A * @return a possible empty list.
1185N/A */
1198N/A public List<String> getCookieVals(String cookieName) {
1185N/A Cookie[] cookies = req.getCookies();
1185N/A ArrayList<String> res = new ArrayList<String>();
1185N/A if (cookies != null) {
1185N/A for (int i = cookies.length - 1; i >= 0; i--) {
1185N/A if (cookies[i].getName().equals(cookieName)) {
1185N/A splitByComma(cookies[i].getValue(), res);
1185N/A }
1185N/A }
1185N/A }
1185N/A return res;
1185N/A }
1185N/A
1185N/A /**
1190N/A * Get the parameter values for the given name. Splits comma separated
1185N/A * values automatically into a list of Strings.
1185N/A * @param name name of the parameter.
1185N/A * @return a possible empty list.
1185N/A */
1198N/A private List<String> getParamVals(String paramName) {
1185N/A String vals[] = req.getParameterValues(paramName);
1198N/A List<String> res = new ArrayList<String>();
1185N/A if (vals != null) {
1185N/A for (int i = vals.length - 1; i >= 0; i--) {
1185N/A splitByComma(vals[i], res);
1185N/A }
1185N/A }
1185N/A return res;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Same as {@link #getRequestedProjects()}, but with a variable cookieName
1185N/A * and parameter name. This way it is trivial to implement a project filter
1185N/A * ...
1190N/A * @param paramName the name of the request parameter, which possibly
1185N/A * contains the project list in question.
1190N/A * @param cookieName name of the cookie which possible contains project
1185N/A * lists used as fallback
1185N/A * @return a possible empty set but never {@code null}.
1185N/A */
1185N/A protected TreeSet<String> getRequestedProjects(String paramName,
1185N/A String cookieName) {
1185N/A TreeSet<String> set = new TreeSet<String>();
1185N/A List<Project> projects = getEnv().getProjects();
1185N/A if (projects == null) {
1185N/A return set;
1185N/A }
1185N/A if (projects.size() == 1) {
1185N/A set.add(projects.get(0).getDescription());
1185N/A return set;
1185N/A }
1198N/A List<String> vals = getParamVals(paramName);
1185N/A for (String s : vals) {
1185N/A if (Project.getByDescription(s) != null) {
1185N/A set.add(s);
1185N/A }
1185N/A }
1185N/A if (set.isEmpty()) {
1185N/A List<String> cookies = getCookieVals(cookieName);
1185N/A for (String s : cookies) {
1185N/A if (Project.getByDescription(s) != null) {
1185N/A set.add(s);
1185N/A }
1185N/A }
1185N/A }
1185N/A if (set.isEmpty()) {
1185N/A Project defaultProject = env.getDefaultProject();
1185N/A if (defaultProject != null) {
1185N/A set.add(defaultProject.getDescription());
1185N/A }
1185N/A }
1185N/A return set;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Set the page title to use.
1185N/A * @param title title to set (might be {@code null}).
1185N/A */
1185N/A public void setTitle(String title) {
1185N/A pageTitle = title;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the page title to use.
1185N/A * @return {@code null} if not set, the page title otherwise.
1185N/A */
1185N/A public String getTitle() {
1185N/A return pageTitle;
1185N/A }
1185N/A
1185N/A /**
1190N/A * Get the base path to use to refer to CSS stylesheets and related
1185N/A * resources. Usually used to create links.
1190N/A *
1190N/A * @return the appropriate application directory prefixed with the
1185N/A * application's context path (e.g. "/source/default").
1185N/A * @see HttpServletRequest#getContextPath()
1185N/A * @see RuntimeEnvironment#getWebappLAF()
1185N/A */
1185N/A public String getCssDir() {
1185N/A return req.getContextPath() + '/' + getEnv().getWebappLAF();
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the current runtime environment.
1190N/A *
1185N/A * @return the runtime env.
1185N/A * @see RuntimeEnvironment#getInstance()
1185N/A * @see RuntimeEnvironment#register()
1185N/A */
1185N/A public RuntimeEnvironment getEnv() {
1185N/A if (env == null) {
1185N/A env = RuntimeEnvironment.getInstance().register();
1185N/A }
1185N/A return env;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the name patterns used to determine, whether a file should be
1185N/A * ignored.
1190N/A *
1185N/A * @return the corresponding value from the current runtime config..
1185N/A */
1185N/A public IgnoredNames getIgnoredNames() {
1185N/A if (ignoredNames == null) {
1185N/A ignoredNames = getEnv().getIgnoredNames();
1185N/A }
1185N/A return ignoredNames;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the canonical path to root of the source tree. File separators are
1185N/A * replaced with a '/'.
1190N/A *
1185N/A * @return The on disk source root directory.
1185N/A * @see RuntimeEnvironment#getSourceRootPath()
1185N/A */
1185N/A public String getSourceRootPath() {
1185N/A if (sourceRootPath == null) {
1194N/A sourceRootPath = getEnv().getSourceRootPath().replace(File.separatorChar, '/');
1185N/A }
1185N/A return sourceRootPath;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the prefix for the related request.
1185N/A * @return {@link Prefix#UNKNOWN} if the servlet path matches any known
1185N/A * prefix, the prefix otherwise.
1185N/A */
1185N/A public Prefix getPrefix() {
1185N/A if (prefix == null) {
1185N/A prefix = Prefix.get(req.getServletPath());
1185N/A }
1185N/A return prefix;
1185N/A }
1185N/A
1185N/A /**
1190N/A * Get the canonical path of the related resource relative to the
1185N/A * source root directory (used file separators are all '/'). No check is
1194N/A * made, whether the obtained path is really an accessible resource on disk.
1190N/A *
1185N/A * @see HttpServletRequest#getPathInfo()
1190N/A * @return a possible empty String (denotes the source root directory) but
1185N/A * not {@code null}.
1185N/A */
1185N/A public String getPath() {
1185N/A if (path == null) {
1185N/A path = Util.getCanonicalPath(req.getPathInfo(), '/');
1194N/A if ("/".equals(path)) {
1185N/A path = "";
1185N/A }
1185N/A }
1185N/A return path;
1185N/A }
1185N/A
1185N/A /**
1185N/A * If a requested resource is not available, append "/on/" to
1185N/A * the source root directory and try again to resolve it.
1190N/A *
1185N/A * @return on success a none-{@code null} gets returned, which should be
1185N/A * used to redirect the client to the propper path.
1185N/A */
1185N/A public String getOnRedirect() {
1185N/A if (check4on) {
1185N/A File newFile = new File(getSourceRootPath() + "/on/" + getPath());
1185N/A if (newFile.canRead()) {
1185N/A return req.getContextPath() + req.getServletPath() + "/on"
1185N/A + getUriEncodedPath()
1185N/A + (newFile.isDirectory() ? trailingSlash(path) : "");
1185N/A }
1185N/A }
1185N/A return null;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the on disk file to the request related file or directory.
1190N/A *
1185N/A * NOTE: If a repository contains hard or symbolic links, the returned
1190N/A * file may finally point to a file outside of the source root directory.
1190N/A *
1185N/A * @return {@code new File("/")} if the related file or directory is not
1190N/A * available (can not be find below the source root directory),
1185N/A * the readable file or directory otherwise.
1185N/A * @see #getSourceRootPath()
1185N/A * @see #getPath()
1185N/A */
1185N/A public File getResourceFile() {
1185N/A if (resourceFile == null) {
1185N/A resourceFile = new File(getSourceRootPath(), getPath());
1185N/A if (!resourceFile.canRead()) {
1185N/A resourceFile = new File("/");
1185N/A }
1185N/A }
1185N/A return resourceFile;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the canonical on disk path to the request related file or directory
1185N/A * with all file separators replaced by a '/'.
1190N/A *
1185N/A * @return "/" if the evaluated path is invalid or outside the source root
1185N/A * directory), otherwise the path to the readable file or directory.
1185N/A * @see #getResourceFile()
1185N/A */
1185N/A public String getResourcePath() {
1185N/A if (resourcePath == null) {
1194N/A resourcePath = getResourceFile().getPath().replace(File.separatorChar, '/');
1185N/A }
1194N/A return resourcePath;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Check, whether the related request resource matches a valid file or
1185N/A * directory below the source root directory and wether it matches an
1185N/A * ignored pattern.
1190N/A *
1185N/A * @return {@code true} if the related resource does not exists or should be
1185N/A * ignored.
1185N/A * @see #getIgnoredNames()
1185N/A * @see #getResourcePath()
1185N/A */
1185N/A public boolean resourceNotAvailable() {
1185N/A getIgnoredNames();
1185N/A return getResourcePath().equals("/") || ignoredNames.ignore(getPath())
1185N/A || ignoredNames.ignore(resourceFile.getParentFile().getName());
1185N/A }
1185N/A
1185N/A /**
1185N/A * Check, whether the request related path represents a directory.
1190N/A *
1185N/A * @return {@code true} if directory related request
1185N/A */
1185N/A public boolean isDir() {
1185N/A if (isDir == null) {
1185N/A isDir = Boolean.valueOf(getResourceFile().isDirectory());
1185N/A }
1185N/A return isDir.booleanValue();
1185N/A }
1185N/A
1185N/A private static String trailingSlash(String path) {
1185N/A return path.length() == 0 || path.charAt(path.length() - 1) != '/'
1185N/A ? "/"
1185N/A : "";
1185N/A }
1185N/A
1185N/A private final File checkFile(File dir, String name, boolean compressed) {
1185N/A File f = null;
1185N/A if (compressed) {
1185N/A f = new File(dir, name + ".gz");
1185N/A if (f.exists() && f.isFile()
1185N/A && f.lastModified() >= resourceFile.lastModified()) {
1185N/A return f;
1185N/A }
1185N/A }
1185N/A f = new File(dir, name);
1185N/A if (f.exists() && f.isFile()
1185N/A && f.lastModified() >= resourceFile.lastModified()) {
1185N/A return f;
1185N/A }
1185N/A return null;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Find the files with the given names in the {@link #getPath()} directory
1185N/A * relative to the crossfile directory of the opengrok data directory. It
1185N/A * is tried to find the compressed file first by appending the file extension
1185N/A * ".gz" to the filename. If that fails or an uncompressed version of the
1185N/A * file is younger than its compressed version, the uncompressed file gets
1190N/A * used.
1190N/A *
1185N/A * @param filenames filenames to lookup.
1185N/A * @return an empty array if the related directory does not exist or the
1190N/A * given list is {@code null} or empty, otherwise an array, which may
1185N/A * contain {@code null} entries (when the related file could not be found)
1185N/A * having the same order as the given list.
1185N/A */
1185N/A public File[] findDataFiles(List<String> filenames) {
1185N/A if (filenames == null || filenames.isEmpty()) {
1185N/A return new File[0];
1185N/A }
1185N/A File[] res = new File[filenames.size()];
1185N/A File dir = new File(getEnv().getDataRootPath() + Prefix.XREF_P + path);
1185N/A if (dir.exists() && dir.isDirectory()) {
1185N/A getResourceFile();
1185N/A boolean compressed = getEnv().isCompressXref();
1185N/A for (int i = res.length - 1; i >= 0; i--) {
1185N/A res[i] = checkFile(dir, filenames.get(i), compressed);
1185N/A }
1185N/A }
1185N/A return res;
1185N/A }
1185N/A
1185N/A /**
1190N/A * Lookup the file {@link #getPath()} relative to the crossfile directory
1190N/A * of the opengrok data directory. It is tried to find the compressed file
1190N/A * first by appending the file extension ".gz" to the filename. If that
1190N/A * fails or an uncompressed version of the file is younger than its
1190N/A * compressed version, the uncompressed file gets used.
1185N/A * @return {@code null} if not found, the file otherwise.
1185N/A */
1185N/A public File findDataFile() {
1185N/A return checkFile(new File(getEnv().getDataRootPath() + Prefix.XREF_P),
1185N/A path, env.isCompressXref());
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the path the request should be redirected (if any).
1190N/A *
1185N/A * @return {@code null} if there is no reason to redirect, the URI encoded
1185N/A * redirect path to use otherwise.
1185N/A */
1185N/A public String getDirectoryRedirect() {
1185N/A if (isDir()) {
1185N/A if (path.length() == 0) {
1185N/A // => /
1185N/A return null;
1185N/A }
1185N/A getPrefix();
1185N/A if (prefix != Prefix.XREF_P && prefix != Prefix.HIST_P) {
1185N/A //if it is an existing dir perhaps people wanted dir xref
1185N/A return req.getContextPath() + Prefix.XREF_P
1185N/A + getUriEncodedPath() + trailingSlash(path);
1185N/A }
1185N/A String ts = trailingSlash(path);
1185N/A if (ts.length() != 0) {
1185N/A return req.getContextPath() + prefix + getUriEncodedPath() + ts;
1185N/A }
1185N/A }
1185N/A return null;
1185N/A }
1185N/A
1185N/A /**
1190N/A * Get the URI encoded canonical path to the related file or directory
1190N/A * (the URI part between the servlet path and the start of the query string).
1190N/A * @return an URI encoded path which might be an empty string but not
1185N/A * {@code null}.
1185N/A * @see #getPath()
1185N/A */
1185N/A public String getUriEncodedPath() {
1185N/A if (uriEncodedPath == null) {
1185N/A uriEncodedPath = Util.URIEncodePath(getPath());
1185N/A }
1185N/A return uriEncodedPath;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get opengrok's configured dataroot directory.
1185N/A * It is veriefied, that the used environment has a valid opengrok data root
1185N/A * set and that it is an accessable directory.
1185N/A * @return the opengrok data directory.
1185N/A * @throws InvalidParameterException if inaccessable or not set.
1185N/A */
1185N/A public File getDataRoot() {
1185N/A if (dataRoot == null) {
1185N/A String tmp = getEnv().getDataRootPath();
1185N/A if (tmp == null || tmp.length() == 0) {
1185N/A throw new InvalidParameterException("dataRoot parameter is not "
1185N/A + "set in configuration.xml!");
1185N/A }
1185N/A dataRoot = new File(tmp);
1185N/A if (!(dataRoot.isDirectory() && dataRoot.canRead())) {
1185N/A throw new InvalidParameterException("The configured dataRoot '"
1185N/A + tmp
1185N/A + "' refers to a none-exsting or unreadable directory!");
1185N/A }
1185N/A }
1185N/A return dataRoot;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Prepare a search helper with all required information, ready to execute
1185N/A * the query implied by the related request parameters and cookies.
1185N/A * <p>
1190N/A * NOTE: One should check the {@link SearchHelper#errorMsg} as well as
1190N/A * {@link SearchHelper#redirect} and take the appropriate action before
1185N/A * executing the prepared query or continue processing.
1185N/A * <p>
1185N/A * This method stops populating fields as soon as an error occurs.
1190N/A *
1185N/A * @return a search helper.
1185N/A */
1185N/A public SearchHelper prepareSearch() {
1185N/A SearchHelper sh = new SearchHelper();
1185N/A sh.dataRoot = getDataRoot(); // throws Exception if none-existent
1185N/A List<SortOrder> sortOrders = getSortOrder();
1185N/A sh.order = sortOrders.isEmpty() ? SortOrder.RELEVANCY : sortOrders.get(0);
1185N/A if (getRequestedProjects().isEmpty() && getEnv().hasProjects()) {
1185N/A sh.errorMsg = "You must select a project!";
1185N/A return sh;
1185N/A }
1185N/A sh.builder = getQueryBuilder();
1185N/A if (sh.builder.getSize() == 0) {
1185N/A // Entry page show the map
1185N/A sh.redirect = req.getContextPath() + '/';
1185N/A return sh;
1185N/A }
1185N/A sh.start = getSearchStart();
1185N/A sh.maxItems = getSearchMaxItems();
1185N/A sh.contextPath = req.getContextPath();
1185N/A // jel: this should be IMHO a config param since not only core dependend
1185N/A sh.parallel = Runtime.getRuntime().availableProcessors() > 1;
1185N/A sh.isCrossRefSearch = getPrefix() == Prefix.SEARCH_R;
1185N/A sh.compressed = env.isCompressXref();
1185N/A sh.desc = getEftarReader();
1185N/A sh.sourceRoot = new File(getSourceRootPath());
1185N/A return sh;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Get the config wrt. the given request. If there is none yet, a new config
1185N/A * gets created, attached to the request and returned.
1185N/A * <p>
1190N/A *
1185N/A * @param request the request to use to initialize the config parameters.
1185N/A * @return always the same none-{@code null} config for a given request.
1185N/A * @throws NullPointerException
1185N/A * if the given parameter is {@code null}.
1185N/A */
1185N/A public static PageConfig get(HttpServletRequest request) {
1185N/A Object cfg = request.getAttribute(ATTR_NAME);
1185N/A if (cfg != null) {
1185N/A return (PageConfig) cfg;
1185N/A }
1185N/A PageConfig pcfg = new PageConfig(request);
1185N/A request.setAttribute(ATTR_NAME, pcfg);
1185N/A return pcfg;
1185N/A }
1185N/A private static final String ATTR_NAME = PageConfig.class.getCanonicalName();
1185N/A private HttpServletRequest req;
1185N/A
1185N/A private PageConfig(HttpServletRequest req) {
1185N/A this.req = req;
1185N/A }
1185N/A
1185N/A /**
1185N/A * Cleanup all allocated resources. Should always be called right before
1185N/A * leaving the _jspService / service.
1185N/A */
1185N/A public void cleanup() {
1185N/A if (req != null) {
1185N/A req.removeAttribute(ATTR_NAME);
1185N/A req = null;
1185N/A }
1185N/A env = null;
1185N/A if (eftarReader != null) {
1194N/A eftarReader.close();
1185N/A }
1185N/A }
1185N/A}