View Javadoc
1   /*
2    * Copyright 2012-2025 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.app.service;
17  
18  import java.io.IOException;
19  import java.io.Reader;
20  import java.io.Writer;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import org.apache.logging.log4j.LogManager;
25  import org.apache.logging.log4j.Logger;
26  import org.codelibs.core.beans.util.BeanUtil;
27  import org.codelibs.core.lang.StringUtil;
28  import org.codelibs.fess.Constants;
29  import org.codelibs.fess.app.pager.BadWordPager;
30  import org.codelibs.fess.mylasta.direction.FessConfig;
31  import org.codelibs.fess.opensearch.client.SearchEngineClient;
32  import org.codelibs.fess.opensearch.config.cbean.BadWordCB;
33  import org.codelibs.fess.opensearch.config.exbhv.BadWordBhv;
34  import org.codelibs.fess.opensearch.config.exentity.BadWord;
35  import org.codelibs.fess.util.ComponentUtil;
36  import org.dbflute.bhv.readable.EntityRowHandler;
37  import org.dbflute.cbean.result.PagingResultBean;
38  import org.dbflute.optional.OptionalEntity;
39  
40  import com.orangesignal.csv.CsvConfig;
41  import com.orangesignal.csv.CsvReader;
42  import com.orangesignal.csv.CsvWriter;
43  
44  import jakarta.annotation.Resource;
45  
46  /**
47   * Service class for bad word management operations.
48   * Provides CRUD operations and CSV import/export functionality for bad words.
49   */
50  public class BadWordService {
51  
52      private static final String DELETE_PREFIX = "--";
53  
54      private static final Logger logger = LogManager.getLogger(BadWordService.class);
55  
56      /** Database behavior for bad word operations. */
57      @Resource
58      protected BadWordBhv badWordBhv;
59  
60      /** Search engine client for index operations. */
61      @Resource
62      protected SearchEngineClient searchEngineClient;
63  
64      /** Fess configuration. */
65      @Resource
66      protected FessConfig fessConfig;
67  
68      /**
69       * Default constructor for BadWordService.
70       */
71      public BadWordService() {
72          // Default constructor
73      }
74  
75      /**
76       * Gets a paginated list of bad words.
77       * @param badWordPager The pager with search criteria and pagination settings.
78       * @return List of bad words matching the criteria.
79       */
80      public List<BadWord> getBadWordList(final BadWordPager badWordPager) {
81  
82          final PagingResultBean<BadWord> badWordList = badWordBhv.selectPage(cb -> {
83              cb.paging(badWordPager.getPageSize(), badWordPager.getCurrentPageNumber());
84              setupListCondition(cb, badWordPager);
85          });
86  
87          // update pager
88          BeanUtil.copyBeanToBean(badWordList, badWordPager, option -> option.include(Constants.PAGER_CONVERSION_RULE));
89          badWordPager.setPageNumberList(
90                  badWordList.pageRange(op -> op.rangeSize(fessConfig.getPagingPageRangeSizeAsInteger())).createPageNumberList());
91  
92          return badWordList;
93      }
94  
95      /**
96       * Gets a bad word by its ID.
97       * @param id The bad word ID.
98       * @return Optional entity containing the bad word if found.
99       */
100     public OptionalEntity<BadWord> getBadWord(final String id) {
101         return badWordBhv.selectByPK(id);
102     }
103 
104     /**
105      * Stores (inserts or updates) a bad word.
106      * @param badWord The bad word to store.
107      */
108     public void store(final BadWord badWord) {
109 
110         badWordBhv.insertOrUpdate(badWord, op -> op.setRefreshPolicy(Constants.TRUE));
111 
112     }
113 
114     /**
115      * Deletes a bad word.
116      * @param badWord The bad word to delete.
117      */
118     public void delete(final BadWord badWord) {
119 
120         badWordBhv.delete(badWord, op -> op.setRefreshPolicy(Constants.TRUE));
121 
122     }
123 
124     /**
125      * Sets up search conditions for bad word list queries.
126      * @param cb The condition bean for the query.
127      * @param badWordPager The pager containing search criteria.
128      */
129     protected void setupListCondition(final BadWordCB cb, final BadWordPager badWordPager) {
130         if (badWordPager.id != null) {
131             cb.query().docMeta().setId_Equal(badWordPager.id);
132         }
133         // TODO Long, Integer, String supported only.
134 
135         // setup condition
136         cb.query().addOrderBy_SuggestWord_Asc();
137 
138         // search
139 
140     }
141 
142     /**
143      * Imports bad words from a CSV file.
144      * @param reader The reader for the CSV data.
145      */
146     public void importCsv(final Reader reader) {
147         @SuppressWarnings("resource")
148         final CsvReader csvReader = new CsvReader(reader, new CsvConfig());
149         try {
150             List<String> list;
151             csvReader.readValues(); // ignore header
152             while ((list = csvReader.readValues()) != null) {
153                 String targetWord = getValue(list, 0);
154                 if (StringUtil.isBlank(targetWord)) {
155                     // skip
156                     continue;
157                 }
158                 try {
159                     boolean isDelete = false;
160                     if (targetWord.startsWith(DELETE_PREFIX)) {
161                         isDelete = true;
162                         targetWord = targetWord.substring(2);
163                     }
164                     final String target = targetWord;
165                     BadWord badWord = badWordBhv.selectEntity(cb -> cb.query().setSuggestWord_Equal(target)).orElse(null);//TODO
166                     final long now = ComponentUtil.getSystemHelper().getCurrentTimeAsLong();
167                     if (isDelete) {
168                         badWordBhv.delete(badWord);
169                     } else if (badWord == null) {
170                         badWord = new BadWord();
171                         badWord.setSuggestWord(targetWord);
172                         badWord.setCreatedBy(Constants.SYSTEM_USER);
173                         badWord.setCreatedTime(now);
174                         badWordBhv.insert(badWord);
175                     } else {
176                         badWord.setUpdatedBy(Constants.SYSTEM_USER);
177                         badWord.setUpdatedTime(now);
178                         badWordBhv.update(badWord);
179                     }
180                 } catch (final Exception e) {
181                     logger.warn("Failed to read a suggest bad word: {}", list, e);
182                 }
183             }
184             searchEngineClient.refresh("_all"); // TODO replace _all
185         } catch (final IOException e) {
186             logger.warn("Failed to read a suggest bad word.", e);
187         }
188     }
189 
190     /**
191      * Exports bad words to a CSV file.
192      * @param writer The writer for the CSV output.
193      */
194     public void exportCsv(final Writer writer) {
195         final CsvConfig cfg = new CsvConfig(',', '"', '"');
196         cfg.setEscapeDisabled(false);
197         cfg.setQuoteDisabled(false);
198         @SuppressWarnings("resource")
199         final CsvWriter csvWriter = new CsvWriter(writer, cfg);
200         try {
201             final List<String> list = new ArrayList<>();
202             list.add("BadWord");
203             csvWriter.writeValues(list);
204 
205             badWordBhv.selectCursor(cb -> cb.query().matchAll(), new EntityRowHandler<BadWord>() {
206                 @Override
207                 public void handle(final BadWord entity) {
208                     final List<String> list = new ArrayList<>();
209                     addToList(list, entity.getSuggestWord());
210                     try {
211                         csvWriter.writeValues(list);
212                     } catch (final IOException e) {
213                         logger.warn("Failed to write a suggest bad word: {}", entity, e);
214                     }
215                 }
216 
217                 private void addToList(final List<String> list, final Object value) {
218                     if (value == null) {
219                         list.add(StringUtil.EMPTY);
220                     } else {
221                         list.add(value.toString());
222                     }
223                 }
224             });
225 
226             csvWriter.flush();
227         } catch (final IOException e) {
228             logger.warn("Failed to write a suggest bad word.", e);
229         }
230     }
231 
232     private static String getValue(final List<String> list, final int index) {
233         if (index >= list.size()) {
234             return StringUtil.EMPTY;
235         }
236         String item = list.get(index).trim();
237         if (StringUtil.isBlank(item)) {
238             return StringUtil.EMPTY;
239         }
240         if (item.length() > 1 && item.charAt(0) == '"' && item.charAt(item.length() - 1) == '"') {
241             item = item.substring(1, item.length() - 1);
242         }
243         return item;
244     }
245 }