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.web.api.admin.dict.mapping;
17  
18  import static org.codelibs.fess.app.web.admin.dict.mapping.AdminDictMappingAction.createCharMappingItem;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.util.stream.Collectors;
24  
25  import org.apache.logging.log4j.LogManager;
26  import org.apache.logging.log4j.Logger;
27  import org.codelibs.fess.app.pager.CharMappingPager;
28  import org.codelibs.fess.app.service.CharMappingService;
29  import org.codelibs.fess.app.web.CrudMode;
30  import org.codelibs.fess.app.web.admin.dict.mapping.UploadForm;
31  import org.codelibs.fess.app.web.api.ApiResult;
32  import org.codelibs.fess.app.web.api.admin.FessApiAdminAction;
33  import org.codelibs.fess.dict.mapping.CharMappingFile;
34  import org.codelibs.fess.dict.mapping.CharMappingItem;
35  import org.lastaflute.web.Execute;
36  import org.lastaflute.web.response.JsonResponse;
37  import org.lastaflute.web.response.StreamResponse;
38  
39  import jakarta.annotation.Resource;
40  
41  /**
42   * API action for CRUD and file operations on dictionary character mappings.
43   */
44  public class ApiAdminDictMappingAction extends FessApiAdminAction {
45  
46      /**
47       * Default constructor.
48       */
49      public ApiAdminDictMappingAction() {
50          super();
51      }
52  
53      private static final Logger logger = LogManager.getLogger(ApiAdminDictMappingAction.class);
54  
55      @Resource
56      private CharMappingService charMappingService;
57  
58      /**
59       * Retrieve list of character mapping entries for the specified dictionary.
60       *
61       * @param dictId identifier of the dictionary
62       * @param body search criteria and paging parameters
63       * @return JSON response containing list of mapping entries
64       */
65      // GET /api/admin/dict/mapping/settings/{dictId}
66      @Execute
67      public JsonResponse<ApiResult> get$settings(final String dictId, final SearchBody body) {
68          body.dictId = dictId;
69          validateApi(body, messages -> {});
70          final CharMappingPager pager = copyBeanToNewBean(body, CharMappingPager.class);
71          return asJson(new ApiResult.ApiConfigsResponse<EditBody>().settings(charMappingService.getCharMappingList(body.dictId, pager)
72                  .stream()
73                  .map(protwordsItem -> createEditBody(protwordsItem, dictId))
74                  .collect(Collectors.toList())).status(ApiResult.Status.OK).result());
75      }
76  
77      /**
78       * Retrieve a single character mapping entry by ID for the specified dictionary.
79       *
80       * @param dictId identifier of the dictionary
81       * @param id identifier of the mapping entry
82       * @return JSON response containing the mapping entry
83       */
84      // GET /api/admin/dict/mapping/setting/{dictId}/{id}
85      @Execute
86      public JsonResponse<ApiResult> get$setting(final String dictId, final long id) {
87          return asJson(new ApiResult.ApiConfigResponse()
88                  .setting(charMappingService.getCharMappingItem(dictId, id).map(entity -> createEditBody(entity, dictId)).orElseGet(() -> {
89                      throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(id)));
90                      return null;
91                  }))
92                  .status(ApiResult.Status.OK)
93                  .result());
94      }
95  
96      /**
97       * Create a new character mapping entry for the specified dictionary.
98       *
99       * @param dictId identifier of the dictionary
100      * @param body create request payload
101      * @return JSON response containing creation result and new entry ID
102      */
103     // POST /api/admin/dict/mapping/setting/{dictId}
104     @Execute
105     public JsonResponse<ApiResult> post$setting(final String dictId, final CreateBody body) {
106         body.dictId = dictId;
107         validateApi(body, messages -> {});
108         body.crudMode = CrudMode.CREATE;
109         final CharMappingItem entity = createCharMappingItem(this, body, () -> {
110             throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL));
111             return null;
112         }).orElseGet(() -> {
113             throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL));
114             return null;
115         });
116         charMappingService.store(body.dictId, entity);
117         return asJson(
118                 new ApiResult.ApiUpdateResponse().id(String.valueOf(entity.getId())).created(true).status(ApiResult.Status.OK).result());
119     }
120 
121     /**
122      * Update an existing character mapping entry in the specified dictionary.
123      *
124      * @param dictId identifier of the dictionary
125      * @param body update request payload
126      * @return JSON response containing update result and entry ID
127      */
128     // PUT /api/admin/dict/mapping/setting/{dictId}
129     @Execute
130     public JsonResponse<ApiResult> put$setting(final String dictId, final EditBody body) {
131         body.dictId = dictId;
132         validateApi(body, messages -> {});
133         body.crudMode = CrudMode.EDIT;
134         final CharMappingItem entity = createCharMappingItem(this, body, () -> {
135             throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToUpdateCrudTable(GLOBAL, String.valueOf(body.id)));
136             return null;
137         }).orElseGet(() -> {
138             throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(body.id)));
139             return null;
140         });
141         charMappingService.store(body.dictId, entity);
142         return asJson(
143                 new ApiResult.ApiUpdateResponse().id(String.valueOf(entity.getId())).created(false).status(ApiResult.Status.OK).result());
144     }
145 
146     /**
147      * Delete a character mapping entry by ID from the specified dictionary.
148      *
149      * @param dictId identifier of the dictionary
150      * @param id identifier of the mapping entry to delete
151      * @return JSON response containing deletion result and entry ID
152      */
153     // DELETE /api/admin/dict/mapping/setting/{dictId}/{id}
154     @Execute
155     public JsonResponse<ApiResult> delete$setting(final String dictId, final long id) {
156         charMappingService.getCharMappingItem(dictId, id).ifPresent(entity -> {
157             charMappingService.delete(dictId, entity);
158             saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
159         }).orElse(() -> {
160             throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(id)));
161         });
162         return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(id)).created(false).status(ApiResult.Status.OK).result());
163     }
164 
165     /**
166      * Upload character mapping file for the specified dictionary.
167      *
168      * @param dictId identifier of the dictionary to upload mapping for
169      * @param form upload form containing the file and metadata
170      * @return JSON response indicating the API result status
171      */
172     // PUT /api/admin/dict/mapping/upload/{dictId}
173     @Execute
174     public JsonResponse<ApiResult> put$upload(final String dictId, final UploadForm form) {
175         form.dictId = dictId;
176         validateApi(form, messages -> {});
177         final CharMappingFile file = charMappingService.getCharMappingFile(form.dictId).orElseGet(() -> {
178             throwValidationErrorApi(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL));
179             return null;
180         });
181         try (InputStream inputStream = form.charMappingFile.getInputStream()) {
182             file.update(inputStream);
183         } catch (final IOException e) {
184             logger.warn("Failed to process a request.", e);
185             throwValidationErrorApi(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL));
186         }
187         return asJson(new ApiResult.ApiResponse().status(ApiResult.Status.OK).result());
188     }
189 
190     /**
191      * Download the character mapping file for the specified dictionary.
192      *
193      * @param dictId identifier of the dictionary
194      * @param body download request payload
195      * @return stream response with file content
196      */
197     // GET /api/admin/dict/mapping/download/{dictId}
198     @Execute
199     public StreamResponse get$download(final String dictId, final DownloadBody body) {
200         body.dictId = dictId;
201         validateApi(body, messages -> {});
202         return charMappingService.getCharMappingFile(body.dictId)
203                 .map(file -> asStream(new File(file.getPath()).getName()).contentTypeOctetStream().stream(out -> {
204                     file.writeOut(out);
205                 }))
206                 .orElseGet(() -> {
207                     throwValidationErrorApi(messages -> messages.addErrorsFailedToDownloadProtwordsFile(GLOBAL));
208                     return null;
209                 });
210     }
211 
212     /**
213      * Create an EditBody DTO from a CharMappingItem entity.
214      *
215      * @param entity source entity with mapping data
216      * @param dictId identifier of the dictionary
217      * @return populated EditBody for API responses
218      */
219     protected EditBody createEditBody(final CharMappingItem entity, final String dictId) {
220         final EditBody body = new EditBody();
221         body.id = entity.getId();
222         body.dictId = dictId;
223         body.inputs = entity.getInputsValue();
224         body.output = entity.getOutput();
225         return body;
226     }
227 }