1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.helper;
17
18 import java.io.BufferedReader;
19 import java.io.ByteArrayInputStream;
20 import java.io.ByteArrayOutputStream;
21 import java.io.IOException;
22 import java.io.InputStreamReader;
23 import java.io.Reader;
24 import java.io.StringReader;
25 import java.util.Base64;
26 import java.util.HashSet;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.zip.GZIPInputStream;
30 import java.util.zip.GZIPOutputStream;
31
32 import javax.annotation.PostConstruct;
33
34 import org.apache.commons.lang3.StringUtils;
35 import org.apache.logging.log4j.LogManager;
36 import org.apache.logging.log4j.Logger;
37 import org.codelibs.core.io.ReaderUtil;
38 import org.codelibs.core.io.SerializeUtil;
39 import org.codelibs.core.lang.StringUtil;
40 import org.codelibs.fess.Constants;
41 import org.codelibs.fess.crawler.builder.RequestDataBuilder;
42 import org.codelibs.fess.crawler.client.CrawlerClient;
43 import org.codelibs.fess.crawler.client.CrawlerClientFactory;
44 import org.codelibs.fess.crawler.entity.RequestData;
45 import org.codelibs.fess.crawler.entity.ResponseData;
46 import org.codelibs.fess.crawler.entity.ResultData;
47 import org.codelibs.fess.crawler.exception.ChildUrlsException;
48 import org.codelibs.fess.crawler.exception.CrawlerSystemException;
49 import org.codelibs.fess.crawler.exception.CrawlingAccessException;
50 import org.codelibs.fess.crawler.extractor.Extractor;
51 import org.codelibs.fess.crawler.extractor.impl.TikaExtractor;
52 import org.codelibs.fess.crawler.processor.ResponseProcessor;
53 import org.codelibs.fess.crawler.processor.impl.DefaultResponseProcessor;
54 import org.codelibs.fess.crawler.rule.Rule;
55 import org.codelibs.fess.crawler.rule.RuleManager;
56 import org.codelibs.fess.crawler.transformer.Transformer;
57 import org.codelibs.fess.crawler.util.TextUtil;
58 import org.codelibs.fess.es.config.exentity.CrawlingConfig;
59 import org.codelibs.fess.es.config.exentity.CrawlingConfig.ConfigName;
60 import org.codelibs.fess.es.config.exentity.CrawlingConfig.Param;
61 import org.codelibs.fess.mylasta.direction.FessConfig;
62 import org.codelibs.fess.util.ComponentUtil;
63 import org.lastaflute.di.core.SingletonLaContainer;
64 import org.lastaflute.di.core.exception.ComponentNotFoundException;
65
66 public class DocumentHelper {
67 private static final Logger logger = LogManager.getLogger(DocumentHelper.class);
68
69 protected static final String SIMILAR_DOC_HASH_PREFIX = "$";
70
71 @PostConstruct
72 public void init() {
73 if (logger.isDebugEnabled()) {
74 logger.debug("Initialize {}", this.getClass().getSimpleName());
75 }
76 try {
77 final TikaExtractor tikaExtractor = ComponentUtil.getComponent("tikaExtractor");
78 if (tikaExtractor != null) {
79 tikaExtractor.setMaxAlphanumTermSize(getMaxAlphanumTermSize());
80 tikaExtractor.setMaxSymbolTermSize(getMaxSymbolTermSize());
81 tikaExtractor.setReplaceDuplication(isDuplicateTermRemoved());
82 tikaExtractor.setSpaceChars(getSpaceChars());
83 }
84 } catch (final ComponentNotFoundException e) {
85 if (logger.isDebugEnabled()) {
86 logger.debug("tikaExtractor is not found: {}", e.getMessage().replace('\n', ' '));
87 }
88 } catch (final Exception e) {
89 logger.warn("Failed to initiaize TikaExtractor.", e);
90 }
91 }
92
93 public String getTitle(final ResponseData responseData, final String title, final Map<String, Object> dataMap) {
94 if (title == null) {
95 return StringUtil.EMPTY;
96 }
97
98 final int[] spaceChars = getSpaceChars();
99 try (final Reader reader = new StringReader(title)) {
100 return TextUtil.normalizeText(reader).initialCapacity(title.length()).spaceChars(spaceChars).execute();
101 } catch (final IOException e) {
102 return StringUtil.EMPTY;
103 }
104 }
105
106 public String getContent(final CrawlingConfig crawlingConfig, final ResponseData responseData, final String content,
107 final Map<String, Object> dataMap) {
108 if (content == null) {
109 return StringUtil.EMPTY;
110 }
111
112 if (crawlingConfig != null) {
113 final Map<String, String> configParam = crawlingConfig.getConfigParameterMap(ConfigName.CONFIG);
114 if (configParam != null && Constants.TRUE.equalsIgnoreCase(configParam.get(Param.Config.KEEP_ORIGINAL_BODY))) {
115 return content;
116 }
117 }
118
119 if (responseData.getMetaDataMap().get(Extractor.class.getSimpleName()) instanceof TikaExtractor) {
120 return content;
121 }
122
123 final int maxAlphanumTermSize = getMaxAlphanumTermSize();
124 final int maxSymbolTermSize = getMaxSymbolTermSize();
125 final boolean duplicateTermRemoved = isDuplicateTermRemoved();
126 final int[] spaceChars = getSpaceChars();
127 try (final Reader reader = new StringReader(content)) {
128 return TextUtil.normalizeText(reader).initialCapacity(content.length()).maxAlphanumTermSize(maxAlphanumTermSize)
129 .maxSymbolTermSize(maxSymbolTermSize).duplicateTermRemoved(duplicateTermRemoved).spaceChars(spaceChars).execute();
130 } catch (final IOException e) {
131 return StringUtil.EMPTY;
132 }
133 }
134
135 protected int getMaxAlphanumTermSize() {
136 final FessConfig fessConfig = ComponentUtil.getFessConfig();
137 return fessConfig.getCrawlerDocumentMaxAlphanumTermSizeAsInteger();
138 }
139
140 protected int getMaxSymbolTermSize() {
141 final FessConfig fessConfig = ComponentUtil.getFessConfig();
142 return fessConfig.getCrawlerDocumentMaxSymbolTermSizeAsInteger();
143 }
144
145 protected boolean isDuplicateTermRemoved() {
146 final FessConfig fessConfig = ComponentUtil.getFessConfig();
147 return fessConfig.isCrawlerDocumentDuplicateTermRemoved();
148 }
149
150 protected int[] getSpaceChars() {
151 final FessConfig fessConfig = ComponentUtil.getFessConfig();
152 return fessConfig.getCrawlerDocumentSpaceCharsAsArray();
153 }
154
155 public String getDigest(final ResponseData responseData, final String content, final Map<String, Object> dataMap, final int maxWidth) {
156 if (content == null) {
157 return StringUtil.EMPTY;
158 }
159
160 String subContent;
161 if (content.length() < maxWidth * 2) {
162 subContent = content;
163 } else {
164 subContent = content.substring(0, maxWidth * 2);
165 }
166
167 final int[] spaceChars = getSpaceChars();
168 try (final Reader reader = new StringReader(subContent)) {
169 final String originalStr = TextUtil.normalizeText(reader).initialCapacity(content.length()).spaceChars(spaceChars).execute();
170 return StringUtils.abbreviate(originalStr, maxWidth);
171 } catch (final IOException e) {
172 return StringUtil.EMPTY;
173 }
174 }
175
176 public Map<String, Object> processRequest(final CrawlingConfig crawlingConfig, final String crawlingInfoId, final String url) {
177 if (StringUtil.isBlank(crawlingInfoId)) {
178 throw new CrawlingAccessException("sessionId is null.");
179 }
180
181 final CrawlerClientFactory crawlerClientFactory = crawlingConfig.initializeClientFactory(ComponentUtil::getCrawlerClientFactory);
182 final CrawlerClient client = crawlerClientFactory.getClient(url);
183 if (client == null) {
184 throw new CrawlingAccessException("CrawlerClient is null for " + url);
185 }
186
187 final long startTime = System.currentTimeMillis();
188 try (final ResponseData responseData = client.execute(RequestDataBuilder.newRequestData().get().url(url).build())) {
189 if (responseData.getRedirectLocation() != null) {
190 final Set<RequestData> childUrlList = new HashSet<>();
191 childUrlList.add(RequestDataBuilder.newRequestData().get().url(responseData.getRedirectLocation()).build());
192 throw new ChildUrlsException(childUrlList, this.getClass().getName() + "#RedirectedFrom:" + url);
193 }
194 responseData.setExecutionTime(System.currentTimeMillis() - startTime);
195 responseData.setSessionId(crawlingInfoId);
196
197 final RuleManager ruleManager = SingletonLaContainer.getComponent(RuleManager.class);
198 final Rule rule = ruleManager.getRule(responseData);
199 if (rule == null) {
200 throw new CrawlingAccessException("No url rule for " + url);
201 }
202 responseData.setRuleId(rule.getRuleId());
203 final ResponseProcessor responseProcessor = rule.getResponseProcessor();
204 if (!(responseProcessor instanceof DefaultResponseProcessor)) {
205 throw new CrawlingAccessException("The response processor is not DefaultResponseProcessor. responseProcessor: "
206 + responseProcessor + ", url: " + url);
207 }
208 final Transformer transformer = ((DefaultResponseProcessor) responseProcessor).getTransformer();
209 final ResultData resultData = transformer.transform(responseData);
210 final byte[] data = resultData.getData();
211 if (data != null) {
212 try {
213 @SuppressWarnings("unchecked")
214 final Map<String, Object> result = (Map<String, Object>) SerializeUtil.fromBinaryToObject(data);
215 return result;
216 } catch (final Exception e) {
217 throw new CrawlerSystemException("Could not create an instance from bytes.", e);
218 }
219 }
220 return null;
221 } catch (final Exception e) {
222 throw new CrawlingAccessException("Failed to parse " + url, e);
223 }
224 }
225
226 public String decodeSimilarDocHash(final String hash) {
227 if (hash != null && hash.startsWith(SIMILAR_DOC_HASH_PREFIX) && hash.length() > SIMILAR_DOC_HASH_PREFIX.length()) {
228 final byte[] decode = Base64.getUrlDecoder().decode(hash.substring(SIMILAR_DOC_HASH_PREFIX.length()));
229 try (BufferedReader reader =
230 new BufferedReader(new InputStreamReader(new GZIPInputStream(new ByteArrayInputStream(decode)), Constants.UTF_8))) {
231 return ReaderUtil.readText(reader);
232 } catch (final IOException e) {
233 if (logger.isDebugEnabled()) {
234 logger.debug("Failed to decode {}", hash, e);
235 }
236 }
237 }
238 return hash;
239 }
240
241 public String encodeSimilarDocHash(final String hash) {
242 if (hash != null && !hash.startsWith(SIMILAR_DOC_HASH_PREFIX)) {
243 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
244 try (GZIPOutputStream gos = new GZIPOutputStream(baos)) {
245 gos.write(hash.getBytes(Constants.UTF_8));
246 }
247 return SIMILAR_DOC_HASH_PREFIX + Base64.getUrlEncoder().withoutPadding().encodeToString(baos.toByteArray());
248 } catch (final IOException e) {
249 logger.warn("Failed to encode {}", hash, e);
250 }
251 }
252 return hash;
253 }
254
255 public String appendLineNumber(final String prefix, final String content) {
256 if (StringUtil.isBlank(content)) {
257 return StringUtil.EMPTY;
258 }
259 final String[] values = content.split("\n");
260 final StringBuilder buf = new StringBuilder((int) (content.length() * 1.3));
261 buf.append(prefix).append(1).append(':').append(values[0]);
262 for (int i = 1; i < values.length; i++) {
263 buf.append('\n').append(prefix).append(i + 1).append(':').append(values[i]);
264 }
265 return buf.toString();
266 }
267 }