1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.taglib;
17
18 import static org.codelibs.core.stream.StreamUtil.stream;
19
20 import java.io.File;
21 import java.math.RoundingMode;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.nio.file.Paths;
25 import java.text.DecimalFormat;
26 import java.text.NumberFormat;
27 import java.text.SimpleDateFormat;
28 import java.time.LocalDateTime;
29 import java.time.ZonedDateTime;
30 import java.time.format.DateTimeFormatter;
31 import java.util.ArrayList;
32 import java.util.Base64;
33 import java.util.Calendar;
34 import java.util.Date;
35 import java.util.Enumeration;
36 import java.util.List;
37 import java.util.Locale;
38 import java.util.Map;
39 import java.util.Objects;
40 import java.util.concurrent.ExecutionException;
41 import java.util.concurrent.TimeUnit;
42 import java.util.regex.Matcher;
43 import java.util.regex.Pattern;
44 import java.util.stream.Collectors;
45
46 import javax.servlet.http.HttpServletRequest;
47
48 import org.apache.commons.lang3.time.DurationFormatUtils;
49 import org.apache.commons.text.StringEscapeUtils;
50 import org.apache.logging.log4j.LogManager;
51 import org.apache.logging.log4j.Logger;
52 import org.apache.pdfbox.util.DateConverter;
53 import org.codelibs.core.lang.StringUtil;
54 import org.codelibs.fesen.common.joda.Joda;
55 import org.codelibs.fess.Constants;
56 import org.codelibs.fess.app.web.base.FessAdminAction;
57 import org.codelibs.fess.app.web.base.login.FessLoginAssist;
58 import org.codelibs.fess.entity.FacetQueryView;
59 import org.codelibs.fess.helper.ViewHelper;
60 import org.codelibs.fess.util.ComponentUtil;
61 import org.lastaflute.di.util.LdiURLUtil;
62 import org.lastaflute.web.util.LaRequestUtil;
63 import org.lastaflute.web.util.LaResponseUtil;
64 import org.lastaflute.web.util.LaServletContextUtil;
65
66 import com.google.common.cache.CacheBuilder;
67 import com.google.common.cache.CacheLoader;
68 import com.google.common.cache.LoadingCache;
69
70 public class FessFunctions {
71 private static final Logger logger = LogManager.getLogger(FessFunctions.class);
72
73 private static final String GEO_PREFIX = "geo.";
74
75 private static final String FACET_PREFIX = "facet.";
76
77 private static final String PDF_DATE = "pdf_date";
78
79 private static final Pattern EMAIL_ADDRESS_PATTERN =
80 Pattern.compile("[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}", Pattern.CASE_INSENSITIVE);
81
82 private static LoadingCache<String, Long> resourceHashCache =
83 CacheBuilder.newBuilder().maximumSize(1000).expireAfterWrite(10, TimeUnit.MINUTES).build(new CacheLoader<String, Long>() {
84 @Override
85 public Long load(final String key) throws Exception {
86 try {
87 final Path path = Paths.get(LaServletContextUtil.getServletContext().getRealPath(key));
88 if (Files.isRegularFile(path)) {
89 return Files.getLastModifiedTime(path).toMillis();
90 }
91 } catch (final Exception e) {
92 logger.debug("Failed to access {}", key, e);
93 }
94 return 0L;
95 }
96 });
97
98 protected FessFunctions() {
99
100 }
101
102 public static Boolean labelExists(final String value) {
103 @SuppressWarnings("unchecked")
104 final Map<String, String> labelValueMap = (Map<String, String>) LaRequestUtil.getRequest().getAttribute(Constants.LABEL_VALUE_MAP);
105 if (labelValueMap != null) {
106 return labelValueMap.get(value) != null;
107 }
108 return false;
109 }
110
111 public static String label(final String value) {
112 @SuppressWarnings("unchecked")
113 final Map<String, String> labelValueMap = (Map<String, String>) LaRequestUtil.getRequest().getAttribute(Constants.LABEL_VALUE_MAP);
114 if (labelValueMap != null) {
115 final String name = labelValueMap.get(value);
116 if (name != null) {
117 return name;
118 }
119 }
120 return value;
121 }
122
123 public static Date date(final Long value) {
124 if (value == null) {
125 return null;
126 }
127 return new Date(value);
128 }
129
130 public static Date parseDate(final String value) {
131 return parseDate(value, Constants.DATE_OPTIONAL_TIME);
132 }
133
134 public static Date parseDate(final String value, final String format) {
135 if (value == null) {
136 return null;
137 }
138
139 try {
140 if (PDF_DATE.equals(format)) {
141 final Calendar cal = DateConverter.toCalendar(value);
142 return cal != null ? cal.getTime() : null;
143 }
144
145 final long time = Joda.forPattern(format).parseMillis(value);
146 return new Date(time);
147 } catch (final Exception e) {
148 return null;
149 }
150 }
151
152 public static String formatDate(final Date date) {
153 if (date == null) {
154 return StringUtil.EMPTY;
155 }
156 final SimpleDateFormat sdf = new SimpleDateFormat(Constants.ISO_DATETIME_FORMAT);
157 sdf.setTimeZone(Constants.TIMEZONE_UTC);
158 return sdf.format(date);
159 }
160
161 public static String formatDate(final LocalDateTime date) {
162 if (date == null) {
163 return StringUtil.EMPTY;
164 }
165 return date.format(DateTimeFormatter.ofPattern(Constants.ISO_DATETIME_FORMAT, Locale.ROOT));
166 }
167
168 public static String formatDate(final ZonedDateTime date, final String format) {
169 if (date == null) {
170 return StringUtil.EMPTY;
171 }
172 return date.format(DateTimeFormatter.ofPattern(format, Locale.ROOT));
173 }
174
175 public static String formatDuration(final long durationMillis) {
176 return DurationFormatUtils.formatDuration(durationMillis, "d 'days' HH:mm:ss.SSS").replace("0 days", StringUtil.EMPTY).trim();
177
178 }
179
180 public static String formatNumber(final long value, final String pattern) {
181 final DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(getUserLocale());
182 df.applyPattern(pattern);
183 return df.format(value);
184 }
185
186 private static Locale getUserLocale() {
187 final Locale locale = ComponentUtil.getRequestManager().getUserLocale();
188 if (locale == null) {
189 return Locale.ROOT;
190 }
191 return locale;
192 }
193
194 public static String formatFileSize(final long value) {
195 double target = value;
196 String unit = "";
197 String format = "0.#";
198 if (value < 1024) {
199 format = "0";
200 } else if (value < 1024L * 1024L) {
201 target /= 1024;
202 unit = "K";
203 } else if (value < 1024L * 1024L * 1024L) {
204 target /= 1024;
205 target /= 1024;
206 unit = "M";
207 } else if (value < 1024L * 1024L * 1024L * 1024L) {
208 target /= 1024;
209 target /= 1024;
210 target /= 1024;
211 unit = "G";
212 } else {
213 target /= 1024;
214 target /= 1024;
215 target /= 1024;
216 target /= 1024;
217 unit = "T";
218 }
219 final DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(getUserLocale());
220 df.applyPattern(format);
221 df.setRoundingMode(RoundingMode.HALF_UP);
222 return df.format(target) + unit;
223 }
224
225 public static String pagingQuery(final String query) {
226 final HttpServletRequest request = LaRequestUtil.getRequest();
227 @SuppressWarnings("unchecked")
228 final List<String> pagingQueryList = (List<String>) request.getAttribute(Constants.PAGING_QUERY_LIST);
229 if (pagingQueryList != null) {
230 final String prefix;
231 if (query != null) {
232 prefix = "ex_q=" + query.split(":")[0] + "%3A";
233 } else {
234 prefix = null;
235 }
236 return pagingQueryList.stream().filter(s -> prefix == null || !s.startsWith(prefix))
237 .collect(Collectors.joining("&", "&", StringUtil.EMPTY));
238 }
239 return StringUtil.EMPTY;
240 }
241
242 public static String facetQuery() {
243 return createQuery(Constants.FACET_QUERY, FACET_PREFIX);
244 }
245
246 public static String geoQuery() {
247 return createQuery(Constants.GEO_QUERY, GEO_PREFIX);
248 }
249
250 public static String facetForm() {
251 return createForm(Constants.FACET_FORM, FACET_PREFIX);
252 }
253
254 public static String geoForm() {
255 return createForm(Constants.GEO_FORM, GEO_PREFIX);
256 }
257
258 public static List<FacetQueryView> facetQueryViewList() {
259 final ViewHelper viewHelper = ComponentUtil.getViewHelper();
260 return viewHelper.getFacetQueryViewList();
261 }
262
263 private static String createQuery(final String key, final String prefix) {
264 final HttpServletRequest request = LaRequestUtil.getRequest();
265 String query = (String) request.getAttribute(key);
266 if (query == null) {
267 final StringBuilder buf = new StringBuilder(100);
268 final Enumeration<String> names = request.getParameterNames();
269 while (names.hasMoreElements()) {
270 final String name = names.nextElement();
271 if (name.startsWith(prefix)) {
272 final String[] values = request.getParameterValues(name);
273 if (values != null) {
274 for (final String value : values) {
275 buf.append('&');
276 buf.append(LdiURLUtil.encode(name, Constants.UTF_8));
277 buf.append('=');
278 buf.append(LdiURLUtil.encode(value, Constants.UTF_8));
279 }
280 }
281 }
282 }
283 query = buf.toString();
284 request.setAttribute(key, query);
285 }
286 return query;
287 }
288
289 private static String createForm(final String key, final String prefix) {
290 final HttpServletRequest request = LaRequestUtil.getRequest();
291 String query = (String) request.getAttribute(key);
292 if (query == null) {
293 final StringBuilder buf = new StringBuilder(100);
294 final Enumeration<String> names = request.getParameterNames();
295 while (names.hasMoreElements()) {
296 final String name = names.nextElement();
297 if (name.startsWith(prefix)) {
298 final String[] values = request.getParameterValues(name);
299 if (values != null) {
300 for (final String value : values) {
301 buf.append("<input type=\"hidden\" name=\"");
302 buf.append(StringEscapeUtils.escapeHtml4(name));
303 buf.append("\" value=\"");
304 buf.append(StringEscapeUtils.escapeHtml4(value));
305 buf.append("\"/>");
306 }
307 }
308 }
309 }
310 query = buf.toString();
311 request.setAttribute(key, query);
312 }
313 return query;
314 }
315
316 public static String base64(final String value) {
317 if (value == null) {
318 return StringUtil.EMPTY;
319 }
320 return Base64.getUrlEncoder().encodeToString(value.getBytes(Constants.CHARSET_UTF_8));
321 }
322
323 public static boolean fileExists(final String path) {
324 final File file = new File(LaServletContextUtil.getServletContext().getRealPath(path));
325 return file.exists();
326 }
327
328 public static String url(final String input) {
329 if (input == null) {
330 final String msg = "The argument 'input' should not be null.";
331 throw new IllegalArgumentException(msg);
332 }
333 if (!input.startsWith("/")) {
334 final String msg = "The argument 'input' should start with slash '/': " + input;
335 throw new IllegalArgumentException(msg);
336 }
337 final String contextPath = LaRequestUtil.getRequest().getContextPath();
338 final StringBuilder sb = new StringBuilder();
339 if (contextPath.length() > 1) {
340 sb.append(contextPath);
341 }
342 sb.append(input);
343 if (input.indexOf('?') == -1) {
344 try {
345 final Long value = resourceHashCache.get(input);
346 if (value.longValue() > 0) {
347 sb.append("?t=").append(value.toString());
348 }
349 } catch (final ExecutionException e) {
350 logger.debug("Failed to access {}", input, e);
351 }
352 }
353 return LaResponseUtil.getResponse().encodeURL(sb.toString());
354 }
355
356 public static String sdh(final String input) {
357 if (StringUtil.isBlank(input)) {
358 return input;
359 }
360 return ComponentUtil.getDocumentHelper().encodeSimilarDocHash(input);
361 }
362
363 public static String join(final Object input) {
364 String[] values = null;
365 if (input instanceof String[]) {
366 values = (String[]) input;
367 } else if (input instanceof List) {
368 values = ((List<?>) input).stream().filter(Objects::nonNull).map(Object::toString).toArray(n -> new String[n]);
369 } else if (input instanceof String) {
370 return input.toString();
371 }
372 if (values != null) {
373 return stream(values).get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).collect(Collectors.joining(" ")));
374 }
375 return StringUtil.EMPTY;
376 }
377
378 public static String replace(final Object input, final String regex, final String replacement) {
379 if (input == null) {
380 return StringUtil.EMPTY;
381 }
382 return input.toString().replaceAll(regex, replacement);
383 }
384
385 public static String formatCode(final String prefix, final String style, final String mimetype, final String input) {
386 if (input == null) {
387 return StringUtil.EMPTY;
388 }
389 final Pattern pattern = Pattern.compile("^" + prefix + "([0-9]+):(.*)$");
390 final String[] values = input.split("\n");
391 final List<String> list = new ArrayList<>(values.length);
392 int lineNum = 0;
393 for (final String line : values) {
394 final Matcher matcher = pattern.matcher(line);
395 if (matcher.matches()) {
396 if (lineNum == 0) {
397 lineNum = Integer.parseInt(matcher.group(1));
398 list.clear();
399 }
400 list.add(matcher.group(2));
401 } else {
402 list.add(line);
403 }
404 }
405 if (lineNum == 0 || list.isEmpty()) {
406 return "<pre class=\"" + style + "\">" + input + "</pre>";
407 }
408 int lastIndex = list.size();
409 if (list.get(list.size() - 1).endsWith("...")) {
410 lastIndex--;
411 }
412 if (lastIndex <= 0) {
413 lastIndex = 1;
414 }
415 final String content = list.subList(0, lastIndex).stream().collect(Collectors.joining("\n"));
416 if (StringUtil.isBlank(content)) {
417 return "<pre class=\"" + style + "\">" + input.replaceAll("L[0-9]+:", StringUtil.EMPTY).trim() + "</pre>";
418 }
419 return "<pre class=\"" + style + " linenums:" + lineNum + "\">" + content + "</pre>";
420 }
421
422 public static String getMessage(final String key, final String defaultValue) {
423 final Locale locale = LaRequestUtil.getOptionalRequest().map(HttpServletRequest::getLocale).orElse(Locale.ROOT);
424 return ComponentUtil.getMessageManager().findMessage(locale, key).orElse(defaultValue);
425 }
426
427 public static boolean hasActionRole(final String role) {
428 final String[] roles;
429 if (role.endsWith(FessAdminAction.VIEW)) {
430 roles = new String[] { role, role.substring(0, role.length() - FessAdminAction.VIEW.length()) };
431 } else {
432 roles = new String[] { role };
433 }
434 final FessLoginAssist loginAssist = ComponentUtil.getComponent(FessLoginAssist.class);
435 return loginAssist.getSavedUserBean()
436 .map(user -> user.hasRoles(roles) || user.hasRoles(ComponentUtil.getFessConfig().getAuthenticationAdminRolesAsArray()))
437 .orElse(false);
438 }
439
440 public static String maskEmail(final String value) {
441 if (value == null) {
442 return StringUtil.EMPTY;
443 }
444 return EMAIL_ADDRESS_PATTERN.matcher(value).replaceAll("******@****.***");
445 }
446 }