View Javadoc
1   /*
2    * Copyright 2012-2021 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
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; // 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; // 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; // 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; // 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; // 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; // 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 }