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.helper;
17  
18  import static org.codelibs.core.stream.StreamUtil.stream;
19  
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Locale;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.regex.Pattern;
29  import java.util.stream.Collectors;
30  
31  import org.apache.logging.log4j.LogManager;
32  import org.apache.logging.log4j.Logger;
33  import org.codelibs.core.lang.StringUtil;
34  import org.codelibs.fess.Constants;
35  import org.codelibs.fess.app.service.LabelTypeService;
36  import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
37  import org.codelibs.fess.opensearch.config.exentity.LabelType;
38  import org.codelibs.fess.util.ComponentUtil;
39  
40  import jakarta.annotation.PostConstruct;
41  
42  /**
43   * Helper class for label types.
44   */
45  public class LabelTypeHelper extends AbstractConfigHelper {
46      private static final Logger logger = LogManager.getLogger(LabelTypeHelper.class);
47  
48      /** A list of label type items. */
49      protected volatile List<LabelTypeItem> labelTypeItemList;
50  
51      /** A list of label type patterns. */
52      protected volatile List<LabelTypePattern> labelTypePatternList;
53  
54      /**
55       * Default constructor.
56       */
57      public LabelTypeHelper() {
58          super();
59      }
60  
61      /**
62       * Initializes the helper.
63       */
64      @PostConstruct
65      public void init() {
66          if (logger.isDebugEnabled()) {
67              logger.debug("Initializing {}", this.getClass().getSimpleName());
68          }
69          load();
70      }
71  
72      @Override
73      public int load() {
74          final List<LabelType> labelTypeList = ComponentUtil.getComponent(LabelTypeService.class).getLabelTypeList();
75          buildLabelTypeItems(labelTypeList);
76          buildLabelTypePatternList(labelTypeList);
77          return labelTypeList.size();
78      }
79  
80      /**
81       * Refreshes the label type items and patterns.
82       *
83       * @param labelTypeList The list of label types.
84       */
85      public void refresh(final List<LabelType> labelTypeList) {
86          buildLabelTypeItems(labelTypeList);
87          buildLabelTypePatternList(labelTypeList);
88      }
89  
90      /**
91       * Builds a list of label type items.
92       *
93       * @param labelTypeList The list of label types.
94       */
95      protected void buildLabelTypeItems(final List<LabelType> labelTypeList) {
96          final List<LabelTypeItem> itemList = new ArrayList<>();
97          for (final LabelType labelType : labelTypeList) {
98              final LabelTypeItem item = new LabelTypeItem();
99              item.setLabel(labelType.getName());
100             item.setValue(labelType.getValue());
101             item.setPermissions(labelType.getPermissions());
102             item.setVirtualHost(labelType.getVirtualHost());
103             item.setLocale(labelType.getLocale());
104             itemList.add(item);
105         }
106         labelTypeItemList = itemList;
107     }
108 
109     /**
110      * Returns a list of label type items.
111      *
112      * @param searchRequestType The search request type.
113      * @return A list of label type items.
114      */
115     public List<Map<String, String>> getLabelTypeItemList(final SearchRequestType searchRequestType) {
116         return getLabelTypeItemList(searchRequestType, Locale.ROOT);
117     }
118 
119     /**
120      * Returns a list of label type items.
121      *
122      * @param searchRequestType The search request type.
123      * @param requestLocale The request locale.
124      * @return A list of label type items.
125      */
126     public List<Map<String, String>> getLabelTypeItemList(final SearchRequestType searchRequestType, final Locale requestLocale) {
127         if (labelTypeItemList == null) {
128             init();
129         }
130 
131         final String virtualHostKey = ComponentUtil.getVirtualHostHelper().getVirtualHostKey();
132         final List<LabelTypeItem> labelList;
133         if (StringUtil.isBlank(virtualHostKey)) {
134             labelList =
135                     labelTypeItemList.stream().filter(item -> matchLocale(requestLocale, item.getLocale())).collect(Collectors.toList());
136         } else {
137             labelList = labelTypeItemList.stream()
138                     .filter(item -> matchLocale(requestLocale, item.getLocale()) && virtualHostKey.equals(item.getVirtualHost()))
139                     .collect(Collectors.toList());
140         }
141 
142         final List<Map<String, String>> itemList = new ArrayList<>();
143         final Set<String> roleSet = ComponentUtil.getRoleQueryHelper().build(searchRequestType);
144         if (roleSet.isEmpty()) {
145             for (final LabelTypeItem item : labelList) {
146                 if (item.getPermissions().length == 0) {
147                     final Map<String, String> map = new HashMap<>(2);
148                     map.put(Constants.ITEM_LABEL, item.getLabel());
149                     map.put(Constants.ITEM_VALUE, item.getValue());
150                     itemList.add(map);
151                 }
152             }
153         } else {
154             for (final LabelTypeItem item : labelList) {
155                 final Set<String> permissions = stream(item.getPermissions()).get(stream -> stream.collect(Collectors.toSet()));
156                 for (final String roleValue : roleSet) {
157                     if (permissions.contains(roleValue)) {
158                         final Map<String, String> map = new HashMap<>(2);
159                         map.put(Constants.ITEM_LABEL, item.getLabel());
160                         map.put(Constants.ITEM_VALUE, item.getValue());
161                         itemList.add(map);
162                         break;
163                     }
164                 }
165             }
166         }
167 
168         return itemList;
169     }
170 
171     /**
172      * Matches the request locale with the target locale.
173      *
174      * @param requestLocale The request locale.
175      * @param targetLocale The target locale.
176      * @return True if the locales match, otherwise false.
177      */
178     protected boolean matchLocale(final Locale requestLocale, final Locale targetLocale) {
179         if (targetLocale.equals(requestLocale) || targetLocale.equals(Locale.ROOT)) {
180             return true;
181         }
182         if (requestLocale == null || !requestLocale.getLanguage().equals(targetLocale.getLanguage())
183                 || targetLocale.getCountry().length() > 0 && !requestLocale.getCountry().equals(targetLocale.getCountry())) {
184             return false;
185         }
186         return true;
187     }
188 
189     /**
190      * Returns a set of matched label values.
191      *
192      * @param path The path to match.
193      * @return A set of matched label values.
194      */
195     public Set<String> getMatchedLabelValueSet(final String path) {
196         if (labelTypePatternList == null) {
197             synchronized (this) {
198                 if (labelTypePatternList == null) {
199                     buildLabelTypePatternList(ComponentUtil.getComponent(LabelTypeService.class).getLabelTypeList());
200                 }
201             }
202         }
203 
204         if (labelTypePatternList.isEmpty()) {
205             return Collections.emptySet();
206         }
207 
208         final Set<String> valueSet = new HashSet<>();
209         for (final LabelTypePattern pattern : labelTypePatternList) {
210             if (pattern.match(path)) {
211                 valueSet.add(pattern.getValue());
212             }
213         }
214         return valueSet;
215     }
216 
217     /**
218      * Builds a list of label type patterns.
219      *
220      * @param labelTypeList The list of label types.
221      */
222     protected void buildLabelTypePatternList(final List<LabelType> labelTypeList) {
223         final List<LabelTypePattern> list = new ArrayList<>();
224         for (final LabelType labelType : labelTypeList) {
225             final String includedPaths = labelType.getIncludedPaths();
226             final String excludedPaths = labelType.getExcludedPaths();
227             if (StringUtil.isNotBlank(includedPaths) || StringUtil.isNotBlank(excludedPaths)) {
228                 try {
229                     list.add(new LabelTypePattern(labelType.getValue(), includedPaths, excludedPaths));
230                 } catch (final Exception e) {
231                     logger.warn("Failed to create label pattern: label={}, includedPaths={}, excludedPaths={}", labelType.getValue(),
232                             includedPaths, excludedPaths, e);
233                 }
234             }
235         }
236         labelTypePatternList = list;
237     }
238 
239     /**
240      * An item of a label type.
241      */
242     protected static class LabelTypeItem {
243         /**
244          * Default constructor.
245          */
246         public LabelTypeItem() {
247             // do nothing
248         }
249 
250         private String label;
251 
252         private String value;
253 
254         private String[] permissions;
255 
256         private String virtualHost;
257 
258         private Locale locale;
259 
260         /**
261          * Returns the label.
262          *
263          * @return The label.
264          */
265         public String getLabel() {
266             return label;
267         }
268 
269         /**
270          * Sets the label.
271          *
272          * @param label The label.
273          */
274         public void setLabel(final String label) {
275             this.label = label;
276         }
277 
278         /**
279          * Returns the value.
280          *
281          * @return The value.
282          */
283         public String getValue() {
284             return value;
285         }
286 
287         /**
288          * Sets the value.
289          *
290          * @param value The value.
291          */
292         public void setValue(final String value) {
293             this.value = value;
294         }
295 
296         /**
297          * Returns the permissions.
298          *
299          * @return The permissions.
300          */
301         public String[] getPermissions() {
302             return permissions;
303         }
304 
305         /**
306          * Sets the permissions.
307          *
308          * @param permissions The permissions.
309          */
310         public void setPermissions(final String[] permissions) {
311             this.permissions = permissions;
312         }
313 
314         /**
315          * Returns the virtual host.
316          *
317          * @return The virtual host.
318          */
319         public String getVirtualHost() {
320             return virtualHost;
321         }
322 
323         /**
324          * Sets the virtual host.
325          *
326          * @param virtualHost The virtual host.
327          */
328         public void setVirtualHost(final String virtualHost) {
329             this.virtualHost = virtualHost;
330         }
331 
332         /**
333          * Returns the locale.
334          *
335          * @return The locale.
336          */
337         public Locale getLocale() {
338             return locale;
339         }
340 
341         /**
342          * Sets the locale.
343          *
344          * @param locale The locale.
345          */
346         public void setLocale(final Locale locale) {
347             this.locale = locale;
348         }
349     }
350 
351     /**
352      * A pattern of a label type.
353      */
354     public static class LabelTypePattern {
355 
356         private final String value;
357 
358         private Pattern includedPaths;
359 
360         private Pattern excludedPaths;
361 
362         /**
363          * Constructs a new label type pattern.
364          *
365          * @param value The value.
366          * @param includedPaths The included paths.
367          * @param excludedPaths The excluded paths.
368          */
369         public LabelTypePattern(final String value, final String includedPaths, final String excludedPaths) {
370             this.value = value;
371 
372             final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
373             if (StringUtil.isNotBlank(includedPaths)) {
374                 final StringBuilder buf = new StringBuilder(100);
375                 char split = 0;
376                 for (final String path : includedPaths.split("\n")) {
377                     if (split == 0) {
378                         split = '|';
379                     } else {
380                         buf.append(split);
381                     }
382                     final String normalizePath = systemHelper.normalizeConfigPath(path);
383                     if (StringUtil.isNotBlank(normalizePath)) {
384                         buf.append(normalizePath);
385                     }
386                 }
387                 this.includedPaths = Pattern.compile(buf.toString());
388             }
389 
390             if (StringUtil.isNotBlank(excludedPaths)) {
391                 final StringBuilder buf = new StringBuilder(100);
392                 char split = 0;
393                 for (final String path : excludedPaths.split("\n")) {
394                     if (split == 0) {
395                         split = '|';
396                     } else {
397                         buf.append(split);
398                     }
399                     final String normalizePath = systemHelper.normalizeConfigPath(path);
400                     if (StringUtil.isNotBlank(normalizePath)) {
401                         buf.append(normalizePath);
402                     }
403                 }
404                 this.excludedPaths = Pattern.compile(buf.toString());
405             }
406         }
407 
408         /**
409          * Returns the value.
410          *
411          * @return The value.
412          */
413         public String getValue() {
414             return value;
415         }
416 
417         /**
418          * Matches the given path.
419          *
420          * @param path The path to match.
421          * @return True if the path matches, otherwise false.
422          */
423         public boolean match(final String path) {
424             if (includedPaths == null) {
425                 if (excludedPaths != null && excludedPaths.matcher(path).matches()) {
426                     if (logger.isDebugEnabled()) {
427                         logger.debug("Path {} matches the exclude path expression {} on {} of label.", path, excludedPaths, value);
428                     }
429                     return false;
430                 }
431                 if (logger.isDebugEnabled()) {
432                     logger.debug("Path {} does not match the exclude path expression {} on {} of label.", path, excludedPaths, value);
433                 }
434                 return true;
435             }
436             if (includedPaths.matcher(path).matches()) {
437                 if (excludedPaths != null && excludedPaths.matcher(path).matches()) {
438                     if (logger.isDebugEnabled()) {
439                         logger.debug("Path {} matches the include/exclude path expression {} on {} of label.", path, excludedPaths, value);
440                     }
441                     return false;
442                 }
443                 if (logger.isDebugEnabled()) {
444                     logger.debug("Path {} matches the include path expression {} on {} of label.", path, excludedPaths, value);
445                 }
446                 return true;
447             }
448             if (logger.isDebugEnabled()) {
449                 logger.debug("Path {} does not match the include path expression {} on {} of label.", path, excludedPaths, value);
450             }
451             return false;
452         }
453 
454     }
455 }