1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * See LICENSE.txt included in this distribution for the specific 9 * language governing permissions and limitations under the License. 10 * 11 * When distributing Covered Code, include this CDDL HEADER in each 12 * file and include the License file at LICENSE.txt. 13 * If applicable, add the following below this CDDL HEADER, with the 14 * fields enclosed by brackets "[]" replaced with your own identifying 15 * information: Portions Copyright [yyyy] [name of copyright owner] 16 * 17 * CDDL HEADER END 18 */ 19 20 /* 21 * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. 22 * Portions Copyright (c) 2017, 2020, Chris Fraire <cfraire@me.com>. 23 * Portions Copyright (c) 2022, Krystof Tulinger <k.tulinger@seznam.cz>. 24 */ 25 package org.opengrok.web; 26 27 import java.util.Comparator; 28 import java.util.Iterator; 29 import java.util.List; 30 import java.util.Map; 31 import java.util.NavigableSet; 32 import java.util.TreeMap; 33 import java.util.TreeSet; 34 35 import org.webjars.WebJarAssetLocator; 36 37 /** 38 * A list-like container for JavasScript sources in JSP/HTML pages. 39 * 40 * @author Krystof Tulinger 41 */ 42 public class Scripts implements Iterable<Scripts.Script> { 43 44 private static final String DEBUG_SUFFIX = "-debug"; 45 private static final String WEBJAR_PATH_PREFIX = "META-INF/resources/"; 46 47 enum Type { 48 MINIFIED, DEBUG 49 } 50 51 /** 52 * A script wrapper. 53 */ 54 public abstract static class Script { 55 56 /** 57 * Represents the script information. Either 58 * <ul> 59 * <li>script HTML src attribute for remote scripts</li> 60 * <li>inline javascript code for inline scripts</li> 61 * </ul> 62 */ 63 protected String scriptData; 64 protected int priority; 65 Script(String scriptData, int priority)66 protected Script(String scriptData, int priority) { 67 this.scriptData = scriptData; 68 this.priority = priority; 69 } 70 toHtml()71 public abstract String toHtml(); 72 getScriptData()73 public String getScriptData() { 74 return scriptData; 75 } 76 getPriority()77 public int getPriority() { 78 return priority; 79 } 80 } 81 82 /** 83 * Script implementing the toHtml() method as an external script resource. 84 */ 85 public static class FileScript extends Script { 86 FileScript(String script, int priority)87 public FileScript(String script, int priority) { 88 super(script, priority); 89 } 90 91 @Override toHtml()92 public String toHtml() { 93 return "\t<script type=\"text/javascript\" src=\"" + 94 this.getScriptData() + 95 "\" data-priority=\"" + 96 this.getPriority() + 97 "\"></script>\n"; 98 } 99 100 } 101 102 protected static final Map<String, Script> SCRIPTS = new TreeMap<>(); 103 104 private static final WebJarAssetLocator assetLocator = new WebJarAssetLocator(); 105 106 /** 107 * Aliases for the page scripts. The path in the FileScript is relatively to 108 * the request's context path. 109 * 110 * @see HttpServletRequest#getContextPath() 111 */ 112 static { 113 putFromWebJar("jquery", "jquery.min.js", 10); 114 putjs("jquery-ui", "js/jquery-ui-1.12.1-custom", 11); 115 putFromWebJar("jquery-tablesorter", "jquery.tablesorter.min.js", 12); 116 putjs("tablesorter-parsers", "js/tablesorter-parsers-0.0.3", 13, true); 117 putjs("searchable-option-list", "js/searchable-option-list-2.0.15", 14, true); 118 putjs("utils", "js/utils-0.0.45", 15, true); 119 putjs("repos", "js/repos-0.0.3", 20, true); 120 putjs("diff", "js/diff-0.0.5", 20, true); 121 putjs("jquery-caret", "js/jquery.caret-1.5.2", 25); 122 } 123 putjs(String key, String pathPrefix, int priority)124 private static void putjs(String key, String pathPrefix, int priority) { 125 putjs(key, pathPrefix, priority, false); 126 } 127 putjs(String key, String pathPrefix, int priority, boolean debug)128 private static void putjs(String key, String pathPrefix, int priority, boolean debug) { 129 SCRIPTS.put(key, new FileScript(pathPrefix + ".min.js", priority)); 130 if (debug) { 131 SCRIPTS.put(key + DEBUG_SUFFIX, new FileScript(pathPrefix + ".js", priority)); 132 } 133 } 134 putFromWebJar(String key, String fileName, int priority)135 private static void putFromWebJar(String key, String fileName, int priority) { 136 String path = assetLocator.getFullPath(fileName); 137 if (path.startsWith(WEBJAR_PATH_PREFIX)) { 138 path = path.substring(WEBJAR_PATH_PREFIX.length()); 139 } 140 SCRIPTS.put(key, new FileScript(path, priority)); 141 } 142 143 private static final Comparator<Script> SCRIPTS_COMPARATOR = Comparator 144 .comparingInt(Script::getPriority) 145 .thenComparing(Script::getScriptData); 146 147 /** 148 * Scripts which will be written to the page. 149 */ 150 private final NavigableSet<Script> outputScripts = new TreeSet<>(SCRIPTS_COMPARATOR); 151 152 /** 153 * Convert the page scripts into HTML. 154 * 155 * @return the HTML 156 */ toHtml()157 public String toHtml() { 158 StringBuilder builder = new StringBuilder(); 159 for (Script entry : this) { 160 builder.append(entry.toHtml()); 161 } 162 return builder.toString(); 163 } 164 165 /** 166 * Return the HTML representation of the page scripts. 167 * 168 * @return the HTML 169 * @see #toHtml() 170 */ 171 @Override toString()172 public String toString() { 173 return toHtml(); 174 } 175 176 /** 177 * Return the size of the page scripts. 178 * 179 * @return the size 180 * @see List#size() 181 */ size()182 public int size() { 183 return outputScripts.size(); 184 } 185 186 /** 187 * Check if there is any script for this page. 188 * 189 * @return true if there is not; false otherwise 190 * @see List#isEmpty() 191 */ isEmpty()192 public boolean isEmpty() { 193 return outputScripts.isEmpty(); 194 } 195 196 /** 197 * Iterator over the page scripts. 198 * 199 * @return the iterator 200 * @see List#iterator() 201 */ 202 @Override iterator()203 public Iterator<Script> iterator() { 204 return outputScripts.iterator(); 205 } 206 207 /** 208 * Add a script which is identified by the name. 209 * 210 * @param contextPath given context path for the used URL 211 * @param scriptName name of the script 212 * @param type type of the script to add 213 * @return true if script was added; false otherwise 214 */ addScript(String contextPath, String scriptName, Type type)215 public boolean addScript(String contextPath, String scriptName, Type type) { 216 contextPath = contextPath == null || contextPath.isEmpty() ? "/" : contextPath + "/"; 217 if (type == Type.DEBUG && SCRIPTS.containsKey(scriptName + DEBUG_SUFFIX)) { 218 addScript(contextPath, scriptName + DEBUG_SUFFIX); 219 return true; 220 } else if (SCRIPTS.containsKey(scriptName)) { 221 addScript(contextPath, scriptName); 222 return true; 223 } 224 return false; 225 } 226 addScript(String contextPath, String scriptName)227 private void addScript(String contextPath, String scriptName) { 228 this.addScript(new FileScript(contextPath + SCRIPTS.get(scriptName).getScriptData(), 229 SCRIPTS.get(scriptName).getPriority())); 230 } 231 232 /** 233 * Add a script to the page, taking the script priority into account. 234 * 235 * @param script the script 236 */ addScript(Script script)237 public void addScript(Script script) { 238 this.outputScripts.add(script); 239 } 240 } 241