Java tutorial
/* * Copyright 2012-2019 CodeLibs Project and the Others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.codelibs.fess.helper; import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.annotation.PostConstruct; import javax.servlet.ServletContext; import javax.servlet.SessionTrackingMode; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.catalina.connector.ClientAbortException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringEscapeUtils; import org.codelibs.core.CoreLibConstants; import org.codelibs.core.io.CloseableUtil; import org.codelibs.core.lang.StringUtil; import org.codelibs.core.misc.DynamicProperties; import org.codelibs.fess.Constants; import org.codelibs.fess.crawler.builder.RequestDataBuilder; import org.codelibs.fess.crawler.client.CrawlerClient; import org.codelibs.fess.crawler.client.CrawlerClientFactory; import org.codelibs.fess.crawler.entity.ResponseData; import org.codelibs.fess.crawler.util.CharUtil; import org.codelibs.fess.entity.FacetQueryView; import org.codelibs.fess.entity.HighlightInfo; import org.codelibs.fess.es.config.exentity.CrawlingConfig; import org.codelibs.fess.exception.FessSystemException; import org.codelibs.fess.helper.UserAgentHelper.UserAgentType; import org.codelibs.fess.mylasta.direction.FessConfig; import org.codelibs.fess.util.ComponentUtil; import org.codelibs.fess.util.DocumentUtil; import org.codelibs.fess.util.ResourceUtil; import org.dbflute.optional.OptionalThing; import org.lastaflute.taglib.function.LaFunctions; import org.lastaflute.web.response.ActionResponse; import org.lastaflute.web.response.StreamResponse; import org.lastaflute.web.ruts.process.ActionRuntime; import org.lastaflute.web.util.LaRequestUtil; import org.lastaflute.web.util.LaResponseUtil; import org.lastaflute.web.util.LaServletContextUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.jknack.handlebars.Context; import com.github.jknack.handlebars.Handlebars; import com.github.jknack.handlebars.Template; import com.github.jknack.handlebars.io.FileTemplateLoader; import com.ibm.icu.text.SimpleDateFormat; public class ViewHelper { private static final Logger logger = LoggerFactory.getLogger(ViewHelper.class); protected static final String SCREEN_WIDTH = "screen_width"; protected static final int TABLET_WIDTH = 768; protected static final String CONTENT_DISPOSITION = "Content-Disposition"; protected static final String HL_CACHE = "hl_cache"; protected static final String QUERIES = "queries"; protected static final String CACHE_MSG = "cache_msg"; protected static final Pattern LOCAL_PATH_PATTERN = Pattern.compile("^file:/+[a-zA-Z]:"); protected static final Pattern SHARED_FOLDER_PATTERN = Pattern.compile("^file:/+[^/]\\."); protected boolean encodeUrlLink = false; protected String urlLinkEncoding = Constants.UTF_8; protected String[] highlightedFields; protected String originalHighlightTagPre = "<em>"; protected String originalHighlightTagPost = "</em>"; protected String highlightTagPre; protected String highlightTagPost; protected boolean useSession = true; protected final Map<String, String> pageCacheMap = new ConcurrentHashMap<>(); protected final Map<String, String> initFacetParamMap = new HashMap<>(); protected final Map<String, String> initGeoParamMap = new HashMap<>(); protected final List<FacetQueryView> facetQueryViewList = new ArrayList<>(); protected String cacheTemplateName = "cache"; protected String escapedHighlightPre = null; protected String escapedHighlightPost = null; protected Set<Integer> hihglightTerminalCharSet = new HashSet<>(); protected ActionHook actionHook = new ActionHook(); protected final Set<String> inlineMimeTypeSet = new HashSet<>(); @PostConstruct public void init() { final FessConfig fessConfig = ComponentUtil.getFessConfig(); escapedHighlightPre = LaFunctions.h(originalHighlightTagPre); escapedHighlightPost = LaFunctions.h(originalHighlightTagPost); highlightTagPre = fessConfig.getQueryHighlightTagPre(); highlightTagPost = fessConfig.getQueryHighlightTagPost(); highlightedFields = fessConfig.getQueryHighlightContentDescriptionFieldsAsArray(); fessConfig.getQueryHighlightTerminalChars().codePoints().forEach(hihglightTerminalCharSet::add); try { final ServletContext servletContext = ComponentUtil.getComponent(ServletContext.class); servletContext.setSessionTrackingModes(fessConfig.getSessionTrackingModesAsSet().stream() .map(SessionTrackingMode::valueOf).collect(Collectors.toSet())); } catch (final Throwable e) { logger.warn("Failed to set SessionTrackingMode.", e); } } public String getContentTitle(final Map<String, Object> document) { final FessConfig fessConfig = ComponentUtil.getFessConfig(); String title = DocumentUtil.getValue(document, fessConfig.getIndexFieldTitle(), String.class); if (StringUtil.isBlank(title)) { title = DocumentUtil.getValue(document, fessConfig.getIndexFieldFilename(), String.class); if (StringUtil.isBlank(title)) { title = DocumentUtil.getValue(document, fessConfig.getIndexFieldUrl(), String.class); } } final int size = fessConfig.getResponseMaxTitleLengthAsInteger(); if (size > -1) { title = StringUtils.abbreviate(title, size); } final String value = LaFunctions.h(title); if (!fessConfig.isResponseHighlightContentTitleEnabled()) { return value; } return getQuerySet().map(querySet -> { final Matcher matcher = Pattern .compile(querySet.stream().map(LaFunctions::h).map(Pattern::quote) .collect(Collectors.joining("|")), Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) .matcher(value); final StringBuffer buf = new StringBuffer(value.length() + 100); while (matcher.find()) { matcher.appendReplacement(buf, highlightTagPre + matcher.group(0) + highlightTagPost); } matcher.appendTail(buf); return buf.toString(); }).orElse(value); } protected OptionalThing<Set<String>> getQuerySet() { return LaRequestUtil.getOptionalRequest().map(req -> { @SuppressWarnings("unchecked") final Set<String> querySet = (Set<String>) req.getAttribute(Constants.HIGHLIGHT_QUERIES); return querySet; }).filter(s -> s != null); } public String getContentDescription(final Map<String, Object> document) { for (final String field : highlightedFields) { final String text = DocumentUtil.getValue(document, field, String.class); if (StringUtil.isNotBlank(text)) { return escapeHighlight(text); } } return StringUtil.EMPTY; } protected String escapeHighlight(final String text) { final String escaped = LaFunctions.h(text); int pos = escaped.indexOf(escapedHighlightPre); while (pos >= 0) { int c = escaped.codePointAt(pos); if (Character.isISOControl(c) || hihglightTerminalCharSet.contains(c)) { break; } pos--; } final String value = escaped.substring(pos + 1); return value.replaceAll(escapedHighlightPre, highlightTagPre).replaceAll(escapedHighlightPost, highlightTagPost); } protected String removeHighlightTag(final String str) { return str.replaceAll(originalHighlightTagPre, StringUtil.EMPTY).replaceAll(originalHighlightTagPost, StringUtil.EMPTY); } public HighlightInfo createHighlightInfo() { return LaRequestUtil.getOptionalRequest().map(req -> { final HighlightInfo highlightInfo = new HighlightInfo(); final String widthStr = req.getParameter(SCREEN_WIDTH); if (StringUtil.isNotBlank(widthStr)) { final int width = Integer.parseInt(widthStr); updateHighlisthInfo(highlightInfo, width); final HttpSession session = req.getSession(false); if (session != null) { session.setAttribute(SCREEN_WIDTH, width); } } else { final HttpSession session = req.getSession(false); if (session != null) { final Integer width = (Integer) session.getAttribute(SCREEN_WIDTH); if (width != null) { updateHighlisthInfo(highlightInfo, width); } } } return highlightInfo; }).orElse(new HighlightInfo()); } protected void updateHighlisthInfo(final HighlightInfo highlightInfo, final int width) { if (width < TABLET_WIDTH) { float ratio = ((float) width) / ((float) TABLET_WIDTH); if (ratio < 0.5) { ratio = 0.5f; } highlightInfo.fragmentSize((int) (highlightInfo.getFragmentSize() * ratio)); } } public String getUrlLink(final Map<String, Object> document) { final FessConfig fessConfig = ComponentUtil.getFessConfig(); String url = DocumentUtil.getValue(document, fessConfig.getIndexFieldUrl(), String.class); if (StringUtil.isBlank(url)) { return "#not-found-" + DocumentUtil.getValue(document, fessConfig.getIndexFieldDocId(), String.class); } final boolean isSmbUrl = url.startsWith("smb:") || url.startsWith("smb1:"); final boolean isFtpUrl = url.startsWith("ftp:"); final boolean isSmbOrFtpUrl = isSmbUrl || isFtpUrl; // replacing url with mapping data url = ComponentUtil.getPathMappingHelper().replaceUrl(url); final boolean isHttpUrl = url.startsWith("http:") || url.startsWith("https:"); if (isSmbUrl) { url = url.replace("smb:", "file:"); url = url.replace("smb1:", "file:"); } if (isHttpUrl && isSmbOrFtpUrl) { // smb/ftp->http // encode final StringBuilder buf = new StringBuilder(url.length() + 100); for (final char c : url.toCharArray()) { if (CharUtil.isUrlChar(c)) { buf.append(c); } else { try { buf.append(URLEncoder.encode(String.valueOf(c), urlLinkEncoding)); } catch (final UnsupportedEncodingException e) { buf.append(c); } } } url = buf.toString(); } else if (url.startsWith("file:")) { // file, smb/ftp->http url = updateFileProtocol(url); if (encodeUrlLink) { return appendQueryParameter(document, url); } // decode if (!isSmbOrFtpUrl) { // file try { url = URLDecoder.decode(url.replace("+", "%2B"), urlLinkEncoding); } catch (final Exception e) { if (logger.isDebugEnabled()) { logger.warn("Failed to decode " + url, e); } } } } // http, ftp // nothing return appendQueryParameter(document, url); } protected String updateFileProtocol(String url) { final int pos = url.indexOf(':', 5); final boolean isLocalFile = pos > 0 && pos < 12; final UserAgentType ua = ComponentUtil.getUserAgentHelper().getUserAgentType(); final DynamicProperties systemProperties = ComponentUtil.getSystemProperties(); switch (ua) { case IE: if (isLocalFile) { url = url.replaceFirst("file:/+", systemProperties.getProperty("file.protocol.winlocal.ie", "file://")); } else { url = url.replaceFirst("file:/+", systemProperties.getProperty("file.protocol.ie", "file://")); } break; case FIREFOX: if (isLocalFile) { url = url.replaceFirst("file:/+", systemProperties.getProperty("file.protocol.winlocal.firefox", "file://")); } else { url = url.replaceFirst("file:/+", systemProperties.getProperty("file.protocol.firefox", "file://///")); } break; case CHROME: if (isLocalFile) { url = url.replaceFirst("file:/+", systemProperties.getProperty("file.protocol.winlocal.chrome", "file://")); } else { url = url.replaceFirst("file:/+", systemProperties.getProperty("file.protocol.chrome", "file://")); } break; case SAFARI: if (isLocalFile) { url = url.replaceFirst("file:/+", systemProperties.getProperty("file.protocol.winlocal.safari", "file://")); } else { url = url.replaceFirst("file:/+", systemProperties.getProperty("file.protocol.safari", "file:////")); } break; case OPERA: if (isLocalFile) { url = url.replaceFirst("file:/+", systemProperties.getProperty("file.protocol.winlocal.opera", "file://")); } else { url = url.replaceFirst("file:/+", systemProperties.getProperty("file.protocol.opera", "file://")); } break; default: if (isLocalFile) { url = url.replaceFirst("file:/+", systemProperties.getProperty("file.protocol.winlocal.other", "file://")); } else { url = url.replaceFirst("file:/+", systemProperties.getProperty("file.protocol.other", "file://")); } break; } return url; } protected String appendQueryParameter(final Map<String, Object> document, final String url) { final FessConfig fessConfig = ComponentUtil.getFessConfig(); if (fessConfig.isAppendQueryParameter()) { if (url.indexOf('#') >= 0) { return url; } final String mimetype = DocumentUtil.getValue(document, fessConfig.getIndexFieldMimetype(), String.class); if (StringUtil.isNotBlank(mimetype)) { if ("application/pdf".equals(mimetype)) { return appendPDFSearchWord(url); } else { // TODO others.. return url; } } } return url; } protected String appendPDFSearchWord(final String url) { final String queries = (String) LaRequestUtil.getRequest().getAttribute(Constants.REQUEST_QUERIES); if (queries != null) { try { final StringBuilder buf = new StringBuilder(url.length() + 100); buf.append(url).append("#search=%22"); buf.append(URLEncoder.encode(queries.trim(), Constants.UTF_8)); buf.append("%22"); return buf.toString(); } catch (final UnsupportedEncodingException e) { logger.warn("Unsupported encoding.", e); } } return url; } public String getPagePath(final String page) { final Locale locale = ComponentUtil.getRequestManager().getUserLocale(); final String lang = locale.getLanguage(); final String country = locale.getCountry(); final String pathLC = getLocalizedPagePath(page, lang, country); final String pLC = pageCacheMap.get(pathLC); if (pLC != null) { return pLC; } if (existsPage(pathLC)) { pageCacheMap.put(pathLC, pathLC); return pathLC; } final String pathL = getLocalizedPagePath(page, lang, null); final String pL = pageCacheMap.get(pathL); if (pL != null) { return pL; } if (existsPage(pathL)) { pageCacheMap.put(pathLC, pathL); return pathL; } final String path = getLocalizedPagePath(page, null, null); final String p = pageCacheMap.get(path); if (p != null) { return p; } if (existsPage(path)) { pageCacheMap.put(pathLC, path); return path; } return "index.jsp"; } private String getLocalizedPagePath(final String page, final String lang, final String country) { final StringBuilder buf = new StringBuilder(100); buf.append("/WEB-INF/view/").append(page); if (StringUtil.isNotBlank(lang)) { buf.append('_').append(lang); if (StringUtil.isNotBlank(country)) { buf.append('_').append(country); } } buf.append(".jsp"); return buf.toString(); } private boolean existsPage(final String path) { final String realPath = LaServletContextUtil.getServletContext().getRealPath(path); final File file = new File(realPath); return file.isFile(); } public String createCacheContent(final Map<String, Object> doc, final String[] queries) { final FessConfig fessConfig = ComponentUtil.getFessConfig(); final FileTemplateLoader loader = new FileTemplateLoader(ResourceUtil.getViewTemplatePath().toFile()); final Handlebars handlebars = new Handlebars(loader); Locale locale = ComponentUtil.getRequestManager().getUserLocale(); if (locale == null) { locale = Locale.ENGLISH; } String url = DocumentUtil.getValue(doc, fessConfig.getIndexFieldUrl(), String.class); if (url == null) { url = ComponentUtil.getMessageManager().getMessage(locale, "labels.search_unknown"); } doc.put(fessConfig.getResponseFieldUrlLink(), getUrlLink(doc)); String createdStr; final Date created = DocumentUtil.getValue(doc, fessConfig.getIndexFieldCreated(), Date.class); if (created != null) { final SimpleDateFormat sdf = new SimpleDateFormat(CoreLibConstants.DATE_FORMAT_ISO_8601_EXTEND); createdStr = sdf.format(created); } else { createdStr = ComponentUtil.getMessageManager().getMessage(locale, "labels.search_unknown"); } doc.put(CACHE_MSG, ComponentUtil.getMessageManager().getMessage(locale, "labels.search_cache_msg", new Object[] { url, createdStr })); doc.put(QUERIES, queries); String cache = DocumentUtil.getValue(doc, fessConfig.getIndexFieldCache(), String.class); if (cache != null) { final String mimetype = DocumentUtil.getValue(doc, fessConfig.getIndexFieldMimetype(), String.class); if (!ComponentUtil.getFessConfig().isHtmlMimetypeForCache(mimetype)) { cache = StringEscapeUtils.escapeHtml4(cache); } cache = ComponentUtil.getPathMappingHelper().replaceUrls(cache); if (queries != null && queries.length > 0) { doc.put(HL_CACHE, replaceHighlightQueries(cache, queries)); } else { doc.put(HL_CACHE, cache); } } else { doc.put(fessConfig.getIndexFieldCache(), StringUtil.EMPTY); doc.put(HL_CACHE, StringUtil.EMPTY); } try { final Template template = handlebars.compile(cacheTemplateName); final Context hbsContext = Context.newContext(doc); return template.apply(hbsContext); } catch (final Exception e) { logger.warn("Failed to create a cache response.", e); } return null; } protected String replaceHighlightQueries(final String cache, final String[] queries) { final StringBuffer buf = new StringBuffer(cache.length() + 100); final StringBuffer segBuf = new StringBuffer(1000); final Pattern p = Pattern.compile("<[^>]+>"); final Matcher m = p.matcher(cache); final String[] regexQueries = new String[queries.length]; final String[] hlQueries = new String[queries.length]; for (int i = 0; i < queries.length; i++) { regexQueries[i] = Pattern.quote(queries[i]); hlQueries[i] = highlightTagPre + queries[i] + highlightTagPost; } while (m.find()) { segBuf.setLength(0); m.appendReplacement(segBuf, StringUtil.EMPTY); String segment = segBuf.toString(); for (int i = 0; i < queries.length; i++) { segment = Pattern.compile(regexQueries[i], Pattern.CASE_INSENSITIVE).matcher(segment) .replaceAll(hlQueries[i]); } buf.append(segment); buf.append(m.group(0)); } segBuf.setLength(0); m.appendTail(segBuf); String segment = segBuf.toString(); for (int i = 0; i < queries.length; i++) { segment = Pattern.compile(regexQueries[i], Pattern.CASE_INSENSITIVE).matcher(segment) .replaceAll(hlQueries[i]); } buf.append(segment); return buf.toString(); } public Object getSitePath(final Map<String, Object> docMap) { final FessConfig fessConfig = ComponentUtil.getFessConfig(); final Object urlLink = docMap.get(fessConfig.getResponseFieldUrlLink()); if (urlLink != null) { final String returnUrl; final String url = urlLink.toString(); if (LOCAL_PATH_PATTERN.matcher(url).find() || SHARED_FOLDER_PATTERN.matcher(url).find()) { returnUrl = url.replaceFirst("^file:/+", ""); } else if (url.startsWith("file:")) { returnUrl = url.replaceFirst("^file:/+", "/"); } else { returnUrl = url.replaceFirst("^[a-zA-Z0-9]*:/+", ""); } final int size = fessConfig.getResponseMaxSitePathLengthAsInteger(); if (size > -1) { return StringUtils.abbreviate(returnUrl, size); } else { return returnUrl; } } return null; } public StreamResponse asContentResponse(final Map<String, Object> doc) { if (logger.isDebugEnabled()) { logger.debug("writing the content of: " + doc); } final FessConfig fessConfig = ComponentUtil.getFessConfig(); final CrawlingConfigHelper crawlingConfigHelper = ComponentUtil.getCrawlingConfigHelper(); final String configId = DocumentUtil.getValue(doc, fessConfig.getIndexFieldConfigId(), String.class); if (configId == null) { throw new FessSystemException("configId is null."); } if (configId.length() < 2) { throw new FessSystemException("Invalid configId: " + configId); } final CrawlingConfig config = crawlingConfigHelper.getCrawlingConfig(configId); if (config == null) { throw new FessSystemException("No crawlingConfig: " + configId); } final String url = DocumentUtil.getValue(doc, fessConfig.getIndexFieldUrl(), String.class); final CrawlerClientFactory crawlerClientFactory = ComponentUtil.getComponent(CrawlerClientFactory.class); config.initializeClientFactory(crawlerClientFactory); final CrawlerClient client = crawlerClientFactory.getClient(url); if (client == null) { throw new FessSystemException("No CrawlerClient: " + configId + ", url: " + url); } return writeContent(configId, url, client); } protected StreamResponse writeContent(final String configId, final String url, final CrawlerClient client) { final StreamResponse response = new StreamResponse(StringUtil.EMPTY); final ResponseData responseData = client .execute(RequestDataBuilder.newRequestData().get().url(url).build()); if (responseData.getHttpStatusCode() == 404) { response.httpStatus(responseData.getHttpStatusCode()); CloseableUtil.closeQuietly(responseData); return response; } writeFileName(response, responseData); writeContentType(response, responseData); writeNoCache(response, responseData); response.stream(out -> { try (final InputStream is = new BufferedInputStream(responseData.getResponseBody())) { out.write(is); } catch (final IOException e) { if (!(e.getCause() instanceof ClientAbortException)) { throw new FessSystemException( "Failed to write a content. configId: " + configId + ", url: " + url, e); } } finally { CloseableUtil.closeQuietly(responseData); } if (logger.isDebugEnabled()) { logger.debug("Finished to write " + url); } }); return response; } protected void writeNoCache(final StreamResponse response, final ResponseData responseData) { response.header("Pragma", "no-cache"); response.header("Cache-Control", "no-cache"); response.header("Expires", "Thu, 01 Dec 1994 16:00:00 GMT"); } protected void writeFileName(final StreamResponse response, final ResponseData responseData) { String charset = responseData.getCharSet(); if (charset == null) { charset = Constants.UTF_8; } final String name; final String url = responseData.getUrl(); final int pos = url.lastIndexOf('/'); try { if (pos >= 0 && pos + 1 < url.length()) { name = URLDecoder.decode(url.substring(pos + 1), charset); } else { name = URLDecoder.decode(url, charset); } final String contentDispositionType; if (inlineMimeTypeSet.contains(responseData.getMimeType())) { contentDispositionType = "inline"; } else { contentDispositionType = "attachment"; } final String encodedName = URLEncoder.encode(name, Constants.UTF_8).replace("+", "%20"); response.header(CONTENT_DISPOSITION, contentDispositionType + "; filename=\"" + name + "\"; filename*=utf-8''" + encodedName); } catch (final Exception e) { logger.warn("Failed to write a filename: " + responseData, e); } } protected void writeContentType(final StreamResponse response, final ResponseData responseData) { final String mimeType = responseData.getMimeType(); if (logger.isDebugEnabled()) { logger.debug("mimeType: " + mimeType); } if (mimeType == null) { response.contentTypeOctetStream(); return; } if (mimeType.startsWith("text/")) { final String charset = LaResponseUtil.getResponse().getCharacterEncoding(); if (charset != null) { response.contentType(mimeType + "; charset=" + charset); return; } } response.contentType(mimeType); } public String getClientIp(final HttpServletRequest request) { final String value = request.getHeader("x-forwarded-for"); if (StringUtil.isNotBlank(value)) { return value; } else { return request.getRemoteAddr(); } } public boolean isUseSession() { return useSession; } public void setUseSession(final boolean useSession) { this.useSession = useSession; } public void addInitFacetParam(final String key, final String value) { initFacetParamMap.put(value, key); } public Map<String, String> getInitFacetParamMap() { return initFacetParamMap; } public void addInitGeoParam(final String key, final String value) { initGeoParamMap.put(value, key); } public Map<String, String> getInitGeoParamMap() { return initGeoParamMap; } public void addFacetQueryView(final FacetQueryView facetQueryView) { facetQueryViewList.add(facetQueryView); } public List<FacetQueryView> getFacetQueryViewList() { return facetQueryViewList; } public void addInlineMimeType(final String mimeType) { inlineMimeTypeSet.add(mimeType); } public ActionHook getActionHook() { return actionHook; } public void setActionHook(final ActionHook actionHook) { this.actionHook = actionHook; } public static class ActionHook { public ActionResponse godHandPrologue(final ActionRuntime runtime, final Function<ActionRuntime, ActionResponse> func) { return func.apply(runtime); } public ActionResponse godHandMonologue(final ActionRuntime runtime, final Function<ActionRuntime, ActionResponse> func) { return func.apply(runtime); } public void godHandEpilogue(final ActionRuntime runtime, final Consumer<ActionRuntime> consumer) { consumer.accept(runtime); } public ActionResponse hookBefore(final ActionRuntime runtime, final Function<ActionRuntime, ActionResponse> func) { return func.apply(runtime); } public void hookFinally(final ActionRuntime runtime, final Consumer<ActionRuntime> consumer) { consumer.accept(runtime); } } public void setEncodeUrlLink(final boolean encodeUrlLink) { this.encodeUrlLink = encodeUrlLink; } public void setUrlLinkEncoding(final String urlLinkEncoding) { this.urlLinkEncoding = urlLinkEncoding; } public void setOriginalHighlightTagPre(final String originalHighlightTagPre) { this.originalHighlightTagPre = originalHighlightTagPre; } public void setOriginalHighlightTagPost(final String originalHighlightTagPost) { this.originalHighlightTagPost = originalHighlightTagPost; } public void setCacheTemplateName(final String cacheTemplateName) { this.cacheTemplateName = cacheTemplateName; } }