Prechádzať zdrojové kódy

fix #957 Admin API: /api/admin/dict

yfujita 8 rokov pred
rodič
commit
4ff90c5f38
32 zmenil súbory, kde vykonal 902 pridanie a 14 odobranie
  1. 4 3
      src/main/java/org/codelibs/fess/app/web/admin/dict/kuromoji/AdminDictKuromojiAction.java
  2. 4 3
      src/main/java/org/codelibs/fess/app/web/admin/dict/mapping/AdminDictMappingAction.java
  3. 5 4
      src/main/java/org/codelibs/fess/app/web/admin/dict/protwords/AdminDictProtwordsAction.java
  4. 3 2
      src/main/java/org/codelibs/fess/app/web/admin/dict/seunjeon/AdminDictSeunjeonAction.java
  5. 3 2
      src/main/java/org/codelibs/fess/app/web/admin/dict/synonym/AdminDictSynonymAction.java
  6. 38 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/ApiAdminDictAction.java
  7. 12 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/ListBody.java
  8. 139 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/kuromoji/ApiAdminDictKuromojiAction.java
  9. 6 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/kuromoji/CreateBody.java
  10. 6 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/kuromoji/DownloadBody.java
  11. 6 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/kuromoji/EditBody.java
  12. 7 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/kuromoji/SearchBody.java
  13. 143 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/mapping/ApiAdminDictMappingAction.java
  14. 6 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/mapping/CreateBody.java
  15. 6 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/mapping/DownloadBody.java
  16. 6 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/mapping/EditBody.java
  17. 7 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/mapping/SearchBody.java
  18. 141 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/protwords/ApiAdminDictProtwordsAction.java
  19. 6 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/protwords/CreateBody.java
  20. 6 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/protwords/DownloadBody.java
  21. 6 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/protwords/EditBody.java
  22. 7 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/protwords/SearchBody.java
  23. 142 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/seunjeon/ApiAdminDictSeunjeonAction.java
  24. 6 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/seunjeon/CreateBody.java
  25. 6 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/seunjeon/DownloadBody.java
  26. 6 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/seunjeon/EditBody.java
  27. 7 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/seunjeon/SearchBody.java
  28. 143 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/synonym/ApiAdminDictSynonymAction.java
  29. 6 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/synonym/CreateBody.java
  30. 6 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/synonym/DownloadBody.java
  31. 6 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/synonym/EditBody.java
  32. 7 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/synonym/SearchBody.java

+ 4 - 3
src/main/java/org/codelibs/fess/app/web/admin/dict/kuromoji/AdminDictKuromojiAction.java

@@ -30,6 +30,7 @@ import org.codelibs.fess.app.web.CrudMode;
 import org.codelibs.fess.app.web.admin.dict.AdminDictAction;
 import org.codelibs.fess.app.web.admin.dict.AdminDictAction;
 import org.codelibs.fess.app.web.base.FessAdminAction;
 import org.codelibs.fess.app.web.base.FessAdminAction;
 import org.codelibs.fess.dict.kuromoji.KuromojiItem;
 import org.codelibs.fess.dict.kuromoji.KuromojiItem;
+import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.RenderDataUtil;
 import org.codelibs.fess.util.RenderDataUtil;
 import org.dbflute.optional.OptionalEntity;
 import org.dbflute.optional.OptionalEntity;
 import org.dbflute.optional.OptionalThing;
 import org.dbflute.optional.OptionalThing;
@@ -331,14 +332,14 @@ public class AdminDictKuromojiAction extends FessAdminAction {
     //                                                                        Assist Logic
     //                                                                        Assist Logic
     //                                                                        ============
     //                                                                        ============
 
 
-    private OptionalEntity<KuromojiItem> getEntity(final CreateForm form) {
+    private static OptionalEntity<KuromojiItem> getEntity(final CreateForm form) {
         switch (form.crudMode) {
         switch (form.crudMode) {
         case CrudMode.CREATE:
         case CrudMode.CREATE:
             final KuromojiItem entity = new KuromojiItem(0, StringUtil.EMPTY, StringUtil.EMPTY, StringUtil.EMPTY, StringUtil.EMPTY);
             final KuromojiItem entity = new KuromojiItem(0, StringUtil.EMPTY, StringUtil.EMPTY, StringUtil.EMPTY, StringUtil.EMPTY);
             return OptionalEntity.of(entity);
             return OptionalEntity.of(entity);
         case CrudMode.EDIT:
         case CrudMode.EDIT:
             if (form instanceof EditForm) {
             if (form instanceof EditForm) {
-                return kuromojiService.getKuromojiItem(form.dictId, ((EditForm) form).id);
+                return ComponentUtil.getComponent(KuromojiService.class).getKuromojiItem(form.dictId, ((EditForm) form).id);
             }
             }
             break;
             break;
         default:
         default:
@@ -347,7 +348,7 @@ public class AdminDictKuromojiAction extends FessAdminAction {
         return OptionalEntity.empty();
         return OptionalEntity.empty();
     }
     }
 
 
-    protected OptionalEntity<KuromojiItem> createKuromojiItem(final CreateForm form) {
+    public static OptionalEntity<KuromojiItem> createKuromojiItem(final CreateForm form) {
         return getEntity(form).map(entity -> {
         return getEntity(form).map(entity -> {
             entity.setNewToken(form.token);
             entity.setNewToken(form.token);
             entity.setNewSegmentation(form.segmentation);
             entity.setNewSegmentation(form.segmentation);

+ 4 - 3
src/main/java/org/codelibs/fess/app/web/admin/dict/mapping/AdminDictMappingAction.java

@@ -32,6 +32,7 @@ import org.codelibs.fess.app.web.CrudMode;
 import org.codelibs.fess.app.web.admin.dict.AdminDictAction;
 import org.codelibs.fess.app.web.admin.dict.AdminDictAction;
 import org.codelibs.fess.app.web.base.FessAdminAction;
 import org.codelibs.fess.app.web.base.FessAdminAction;
 import org.codelibs.fess.dict.mapping.CharMappingItem;
 import org.codelibs.fess.dict.mapping.CharMappingItem;
+import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.RenderDataUtil;
 import org.codelibs.fess.util.RenderDataUtil;
 import org.dbflute.optional.OptionalEntity;
 import org.dbflute.optional.OptionalEntity;
 import org.dbflute.optional.OptionalThing;
 import org.dbflute.optional.OptionalThing;
@@ -342,7 +343,7 @@ public class AdminDictMappingAction extends FessAdminAction {
             return OptionalEntity.of(entity);
             return OptionalEntity.of(entity);
         case CrudMode.EDIT:
         case CrudMode.EDIT:
             if (form instanceof EditForm) {
             if (form instanceof EditForm) {
-                return charMappingService.getCharMappingItem(form.dictId, ((EditForm) form).id);
+                return ComponentUtil.getComponent(CharMappingService.class).getCharMappingItem(form.dictId, ((EditForm) form).id);
             }
             }
             break;
             break;
         default:
         default:
@@ -351,7 +352,7 @@ public class AdminDictMappingAction extends FessAdminAction {
         return OptionalEntity.empty();
         return OptionalEntity.empty();
     }
     }
 
 
-    protected OptionalEntity<CharMappingItem> createCharMappingItem(final CreateForm form, final VaErrorHook hook) {
+    public OptionalEntity<CharMappingItem> createCharMappingItem(final CreateForm form, final VaErrorHook hook) {
         return getEntity(form).map(entity -> {
         return getEntity(form).map(entity -> {
             final String[] newInputs = splitLine(form.inputs);
             final String[] newInputs = splitLine(form.inputs);
             validateMappingString(newInputs, "inputs", hook);
             validateMappingString(newInputs, "inputs", hook);
@@ -391,7 +392,7 @@ public class AdminDictMappingAction extends FessAdminAction {
         }
         }
     }
     }
 
 
-    private String[] splitLine(final String value) {
+    private static String[] splitLine(final String value) {
         if (StringUtil.isBlank(value)) {
         if (StringUtil.isBlank(value)) {
             return StringUtil.EMPTY_STRINGS;
             return StringUtil.EMPTY_STRINGS;
         }
         }

+ 5 - 4
src/main/java/org/codelibs/fess/app/web/admin/dict/protwords/AdminDictProtwordsAction.java

@@ -30,6 +30,7 @@ import org.codelibs.fess.app.web.CrudMode;
 import org.codelibs.fess.app.web.admin.dict.AdminDictAction;
 import org.codelibs.fess.app.web.admin.dict.AdminDictAction;
 import org.codelibs.fess.app.web.base.FessAdminAction;
 import org.codelibs.fess.app.web.base.FessAdminAction;
 import org.codelibs.fess.dict.protwords.ProtwordsItem;
 import org.codelibs.fess.dict.protwords.ProtwordsItem;
+import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.RenderDataUtil;
 import org.codelibs.fess.util.RenderDataUtil;
 import org.dbflute.optional.OptionalEntity;
 import org.dbflute.optional.OptionalEntity;
 import org.dbflute.optional.OptionalThing;
 import org.dbflute.optional.OptionalThing;
@@ -309,14 +310,14 @@ public class AdminDictProtwordsAction extends FessAdminAction {
     //                                                                        Assist Logic
     //                                                                        Assist Logic
     //                                                                        ============
     //                                                                        ============
 
 
-    private OptionalEntity<ProtwordsItem> getEntity(final CreateForm form) {
+    private static OptionalEntity<ProtwordsItem> getEntity(final CreateForm form) {
         switch (form.crudMode) {
         switch (form.crudMode) {
         case CrudMode.CREATE:
         case CrudMode.CREATE:
             final ProtwordsItem entity = new ProtwordsItem(0, StringUtil.EMPTY);
             final ProtwordsItem entity = new ProtwordsItem(0, StringUtil.EMPTY);
             return OptionalEntity.of(entity);
             return OptionalEntity.of(entity);
         case CrudMode.EDIT:
         case CrudMode.EDIT:
             if (form instanceof EditForm) {
             if (form instanceof EditForm) {
-                return protwordsService.getProtwordsItem(form.dictId, ((EditForm) form).id);
+                return ComponentUtil.getComponent(ProtwordsService.class).getProtwordsItem(form.dictId, ((EditForm) form).id);
             }
             }
             break;
             break;
         default:
         default:
@@ -325,7 +326,7 @@ public class AdminDictProtwordsAction extends FessAdminAction {
         return OptionalEntity.empty();
         return OptionalEntity.empty();
     }
     }
 
 
-    protected OptionalEntity<ProtwordsItem> createProtwordsItem(final CreateForm form, final VaErrorHook hook) {
+    public static OptionalEntity<ProtwordsItem> createProtwordsItem(final CreateForm form, final VaErrorHook hook) {
         return getEntity(form).map(entity -> {
         return getEntity(form).map(entity -> {
             final String newInput = form.input;
             final String newInput = form.input;
             validateProtwordsString(newInput, "input", hook);
             validateProtwordsString(newInput, "input", hook);
@@ -345,7 +346,7 @@ public class AdminDictProtwordsAction extends FessAdminAction {
         }
         }
     }
     }
 
 
-    private void validateProtwordsString(final String values, final String propertyName, final VaErrorHook hook) {
+    private static void validateProtwordsString(final String values, final String propertyName, final VaErrorHook hook) {
         if (values.length() == 0) {
         if (values.length() == 0) {
             return;
             return;
         }
         }

+ 3 - 2
src/main/java/org/codelibs/fess/app/web/admin/dict/seunjeon/AdminDictSeunjeonAction.java

@@ -32,6 +32,7 @@ import org.codelibs.fess.app.web.CrudMode;
 import org.codelibs.fess.app.web.admin.dict.AdminDictAction;
 import org.codelibs.fess.app.web.admin.dict.AdminDictAction;
 import org.codelibs.fess.app.web.base.FessAdminAction;
 import org.codelibs.fess.app.web.base.FessAdminAction;
 import org.codelibs.fess.dict.seunjeon.SeunjeonItem;
 import org.codelibs.fess.dict.seunjeon.SeunjeonItem;
+import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.RenderDataUtil;
 import org.codelibs.fess.util.RenderDataUtil;
 import org.dbflute.optional.OptionalEntity;
 import org.dbflute.optional.OptionalEntity;
 import org.dbflute.optional.OptionalThing;
 import org.dbflute.optional.OptionalThing;
@@ -319,7 +320,7 @@ public class AdminDictSeunjeonAction extends FessAdminAction {
             return OptionalEntity.of(entity);
             return OptionalEntity.of(entity);
         case CrudMode.EDIT:
         case CrudMode.EDIT:
             if (form instanceof EditForm) {
             if (form instanceof EditForm) {
-                return seunjeonService.getSeunjeonItem(form.dictId, ((EditForm) form).id);
+                return ComponentUtil.getComponent(SeunjeonService.class).getSeunjeonItem(form.dictId, ((EditForm) form).id);
             }
             }
             break;
             break;
         default:
         default:
@@ -328,7 +329,7 @@ public class AdminDictSeunjeonAction extends FessAdminAction {
         return OptionalEntity.empty();
         return OptionalEntity.empty();
     }
     }
 
 
-    protected OptionalEntity<SeunjeonItem> createSeunjeonItem(final CreateForm form, final VaErrorHook hook) {
+    public OptionalEntity<SeunjeonItem> createSeunjeonItem(final CreateForm form, final VaErrorHook hook) {
         return getEntity(form).map(entity -> {
         return getEntity(form).map(entity -> {
             final String[] newInputs = splitLine(form.inputs);
             final String[] newInputs = splitLine(form.inputs);
             validateSeunjeonString(newInputs, "inputs", hook);
             validateSeunjeonString(newInputs, "inputs", hook);

+ 3 - 2
src/main/java/org/codelibs/fess/app/web/admin/dict/synonym/AdminDictSynonymAction.java

@@ -32,6 +32,7 @@ import org.codelibs.fess.app.web.CrudMode;
 import org.codelibs.fess.app.web.admin.dict.AdminDictAction;
 import org.codelibs.fess.app.web.admin.dict.AdminDictAction;
 import org.codelibs.fess.app.web.base.FessAdminAction;
 import org.codelibs.fess.app.web.base.FessAdminAction;
 import org.codelibs.fess.dict.synonym.SynonymItem;
 import org.codelibs.fess.dict.synonym.SynonymItem;
+import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.RenderDataUtil;
 import org.codelibs.fess.util.RenderDataUtil;
 import org.dbflute.optional.OptionalEntity;
 import org.dbflute.optional.OptionalEntity;
 import org.dbflute.optional.OptionalThing;
 import org.dbflute.optional.OptionalThing;
@@ -342,7 +343,7 @@ public class AdminDictSynonymAction extends FessAdminAction {
             return OptionalEntity.of(entity);
             return OptionalEntity.of(entity);
         case CrudMode.EDIT:
         case CrudMode.EDIT:
             if (form instanceof EditForm) {
             if (form instanceof EditForm) {
-                return synonymService.getSynonymItem(form.dictId, ((EditForm) form).id);
+                return ComponentUtil.getComponent(SynonymService.class).getSynonymItem(form.dictId, ((EditForm) form).id);
             }
             }
             break;
             break;
         default:
         default:
@@ -351,7 +352,7 @@ public class AdminDictSynonymAction extends FessAdminAction {
         return OptionalEntity.empty();
         return OptionalEntity.empty();
     }
     }
 
 
-    protected OptionalEntity<SynonymItem> createSynonymItem(final CreateForm form, final VaErrorHook hook) {
+    public OptionalEntity<SynonymItem> createSynonymItem(final CreateForm form, final VaErrorHook hook) {
         return getEntity(form).map(entity -> {
         return getEntity(form).map(entity -> {
             final String[] newInputs = splitLine(form.inputs);
             final String[] newInputs = splitLine(form.inputs);
             validateSynonymString(newInputs, "inputs", hook);
             validateSynonymString(newInputs, "inputs", hook);

+ 38 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/ApiAdminDictAction.java

@@ -0,0 +1,38 @@
+package org.codelibs.fess.app.web.api.admin.dict;
+
+import org.codelibs.fess.app.web.api.ApiResult;
+import org.codelibs.fess.app.web.api.admin.FessApiAdminAction;
+import org.codelibs.fess.dict.DictionaryFile;
+import org.codelibs.fess.dict.DictionaryItem;
+import org.codelibs.fess.dict.DictionaryManager;
+import org.lastaflute.web.Execute;
+import org.lastaflute.web.response.JsonResponse;
+
+import javax.annotation.Resource;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class ApiAdminDictAction extends FessApiAdminAction {
+    @Resource
+    protected DictionaryManager dictionaryManager;
+
+    // GET /api/admin/dict
+    // POST /api/admin/dict
+    @Execute
+    public JsonResponse<ApiResult> settings(final ListBody body) {
+        validateApi(body, messages -> {});
+        final DictionaryFile<? extends DictionaryItem>[] dictFiles = dictionaryManager.getDictionaryFiles();
+        return asJson(new ApiResult.ApiConfigsResponse<ListBody>()
+                .settings(Stream.of(dictFiles).map(dictionaryFile -> createListBody(dictionaryFile)).collect(Collectors.toList()))
+                .status(ApiResult.Status.OK).result());
+    }
+
+    protected ListBody createListBody(DictionaryFile<? extends DictionaryItem> dictionaryFile) {
+        final ListBody body = new ListBody();
+        body.id = dictionaryFile.getId();
+        body.type = dictionaryFile.getType();
+        body.path = dictionaryFile.getPath();
+        body.timestamp = dictionaryFile.getTimestamp();
+        return body;
+    }
+}

+ 12 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/ListBody.java

@@ -0,0 +1,12 @@
+package org.codelibs.fess.app.web.api.admin.dict;
+
+import org.codelibs.fess.app.web.admin.dict.ListForm;
+
+import java.util.Date;
+
+public class ListBody extends ListForm {
+    public String id;
+    public String type;
+    public String path;
+    public Date timestamp;
+}

+ 139 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/kuromoji/ApiAdminDictKuromojiAction.java

@@ -0,0 +1,139 @@
+package org.codelibs.fess.app.web.api.admin.dict.kuromoji;
+
+import org.codelibs.fess.app.pager.KuromojiPager;
+import org.codelibs.fess.app.service.KuromojiService;
+import org.codelibs.fess.app.web.CrudMode;
+import org.codelibs.fess.app.web.admin.dict.kuromoji.UploadForm;
+import org.codelibs.fess.app.web.api.ApiResult;
+import org.codelibs.fess.app.web.api.admin.FessApiAdminAction;
+import org.codelibs.fess.dict.kuromoji.KuromojiFile;
+import org.codelibs.fess.dict.kuromoji.KuromojiItem;
+import org.lastaflute.web.Execute;
+import org.lastaflute.web.response.JsonResponse;
+import org.lastaflute.web.response.StreamResponse;
+
+import javax.annotation.Resource;
+
+import java.io.*;
+import java.util.stream.Collectors;
+import static org.codelibs.fess.app.web.admin.dict.kuromoji.AdminDictKuromojiAction.*;
+
+public class ApiAdminDictKuromojiAction extends FessApiAdminAction {
+
+    @Resource
+    private KuromojiService kuromojiService;
+
+    // GET /api/admin/dict/kuromoji/settings/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> get$settings(final String dictId, final SearchBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        KuromojiPager pager = new KuromojiPager();
+        if (body.pageNumber != null) {
+            pager.setCurrentPageNumber(body.pageNumber);
+        }
+        return asJson(new ApiResult.ApiConfigsResponse<EditBody>()
+                .settings(
+                        kuromojiService.getKuromojiList(body.dictId, pager).stream()
+                                .map(protwordsItem -> createEditBody(protwordsItem, dictId)).collect(Collectors.toList()))
+                .status(ApiResult.Status.OK).result());
+    }
+
+    // GET /api/admin/dict/kuromoji/setting/{dictId}/{id}
+    @Execute
+    public JsonResponse<ApiResult> get$setting(final String dictId, final long id) {
+        return asJson(new ApiResult.ApiConfigResponse()
+                .setting(kuromojiService.getKuromojiItem(dictId, id).map(entity -> createEditBody(entity, dictId)).orElseGet(() -> {
+                    throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(id)));
+                    return null;
+                })).status(ApiResult.Status.OK).result());
+    }
+
+    // PUT /api/admin/dict/kuromoji/setting/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> put$setting(final String dictId, final CreateBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        body.crudMode = CrudMode.CREATE;
+        final KuromojiItem entity = createKuromojiItem(body).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL));
+            return null;
+        });
+        kuromojiService.store(body.dictId, entity);
+        return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(entity.getId())).created(true).status(ApiResult.Status.OK)
+                .result());
+    }
+
+    // POST /api/admin/dict/kuromoji/setting/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> post$setting(final String dictId, final EditBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        body.crudMode = CrudMode.EDIT;
+        final KuromojiItem entity = createKuromojiItem(body).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(body.id)));
+            return null;
+        });
+        kuromojiService.store(body.dictId, entity);
+        return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(entity.getId())).created(false).status(ApiResult.Status.OK)
+                .result());
+    }
+
+    // DELETE /api/admin/dict/kuromoji/setting/{dictId}/{id}
+    @Execute
+    public JsonResponse<ApiResult> delete$setting(final String dictId, final long id) {
+        kuromojiService.getKuromojiItem(dictId, id).ifPresent(entity -> {
+            kuromojiService.delete(dictId, entity);
+            saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
+        }).orElse(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(id)));
+        });
+        return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(id)).created(false).status(ApiResult.Status.OK).result());
+    }
+
+    // POST /api/admin/dict/kuromoji/upload/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> post$upload(final String dictId, final UploadForm form) {
+        form.dictId = dictId;
+        validateApi(form, messages -> {});
+        final KuromojiFile file = kuromojiService.getKuromojiFile(form.dictId).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL));
+            return null;
+        });
+        try (InputStream inputStream = form.kuromojiFile.getInputStream()) {
+            file.update(inputStream);
+        } catch (final Throwable e) {
+            e.printStackTrace();
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL));
+        }
+        return asJson(new ApiResult.ApiResponse().status(ApiResult.Status.OK).result());
+    }
+
+    // GET /api/admin/dict/kuromoji/download/{dictId}
+    @Execute
+    public StreamResponse get$download(final String dictId, final DownloadBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        return kuromojiService.getKuromojiFile(body.dictId).map(file -> {
+            return asStream(new File(file.getPath()).getName()).contentTypeOctetStream().stream(out -> {
+                try (InputStream inputStream = file.getInputStream()) {
+                    out.write(inputStream);
+                }
+            });
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToDownloadProtwordsFile(GLOBAL));
+            return null;
+        });
+    }
+
+    protected EditBody createEditBody(final KuromojiItem entity, final String dictId) {
+        final EditBody body = new EditBody();
+        body.id = entity.getId();
+        body.dictId = dictId;
+        body.token = entity.getToken();
+        body.reading = entity.getReading();
+        body.pos = entity.getPos();
+        body.segmentation = entity.getSegmentation();
+        return body;
+    }
+}

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/kuromoji/CreateBody.java

@@ -0,0 +1,6 @@
+package org.codelibs.fess.app.web.api.admin.dict.kuromoji;
+
+import org.codelibs.fess.app.web.admin.dict.kuromoji.CreateForm;
+
+public class CreateBody extends CreateForm {
+}

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/kuromoji/DownloadBody.java

@@ -0,0 +1,6 @@
+package org.codelibs.fess.app.web.api.admin.dict.kuromoji;
+
+import org.codelibs.fess.app.web.admin.dict.kuromoji.DownloadForm;
+
+public class DownloadBody extends DownloadForm {
+}

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/kuromoji/EditBody.java

@@ -0,0 +1,6 @@
+package org.codelibs.fess.app.web.api.admin.dict.kuromoji;
+
+import org.codelibs.fess.app.web.admin.dict.kuromoji.EditForm;
+
+public class EditBody extends EditForm {
+}

+ 7 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/kuromoji/SearchBody.java

@@ -0,0 +1,7 @@
+package org.codelibs.fess.app.web.api.admin.dict.kuromoji;
+
+import org.codelibs.fess.app.web.admin.dict.kuromoji.SearchForm;
+
+public class SearchBody extends SearchForm {
+    public Integer pageNumber;
+}

+ 143 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/mapping/ApiAdminDictMappingAction.java

@@ -0,0 +1,143 @@
+package org.codelibs.fess.app.web.api.admin.dict.mapping;
+
+import org.codelibs.fess.app.pager.CharMappingPager;
+import org.codelibs.fess.app.service.CharMappingService;
+import org.codelibs.fess.app.web.CrudMode;
+import org.codelibs.fess.app.web.admin.dict.mapping.AdminDictMappingAction;
+import org.codelibs.fess.app.web.admin.dict.mapping.UploadForm;
+import org.codelibs.fess.app.web.api.ApiResult;
+import org.codelibs.fess.app.web.api.admin.FessApiAdminAction;
+import org.codelibs.fess.dict.mapping.CharMappingFile;
+import org.codelibs.fess.dict.mapping.CharMappingItem;
+import org.lastaflute.web.Execute;
+import org.lastaflute.web.response.JsonResponse;
+import org.lastaflute.web.response.StreamResponse;
+
+import javax.annotation.Resource;
+
+import java.io.*;
+import java.util.stream.Collectors;
+
+public class ApiAdminDictMappingAction extends FessApiAdminAction {
+
+    @Resource
+    private CharMappingService charMappingService;
+
+    // GET /api/admin/dict/mapping/settings/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> get$settings(final String dictId, final SearchBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        CharMappingPager pager = new CharMappingPager();
+        if (body.pageNumber != null) {
+            pager.setCurrentPageNumber(body.pageNumber);
+        }
+        return asJson(new ApiResult.ApiConfigsResponse<EditBody>()
+                .settings(
+                        charMappingService.getCharMappingList(body.dictId, pager).stream()
+                                .map(protwordsItem -> createEditBody(protwordsItem, dictId)).collect(Collectors.toList()))
+                .status(ApiResult.Status.OK).result());
+    }
+
+    // GET /api/admin/dict/mapping/setting/{dictId}/{id}
+    @Execute
+    public JsonResponse<ApiResult> get$setting(final String dictId, final long id) {
+        return asJson(new ApiResult.ApiConfigResponse()
+                .setting(charMappingService.getCharMappingItem(dictId, id).map(entity -> createEditBody(entity, dictId)).orElseGet(() -> {
+                    throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(id)));
+                    return null;
+                })).status(ApiResult.Status.OK).result());
+    }
+
+    // PUT /api/admin/dict/mapping/setting/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> put$setting(final String dictId, final CreateBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        body.crudMode = CrudMode.CREATE;
+        final CharMappingItem entity = new AdminDictMappingAction().createCharMappingItem(body, () -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL));
+            return null;
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL));
+            return null;
+        });
+        charMappingService.store(body.dictId, entity);
+        return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(entity.getId())).created(true).status(ApiResult.Status.OK)
+                .result());
+    }
+
+    // POST /api/admin/dict/mapping/setting/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> post$setting(final String dictId, final EditBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        body.crudMode = CrudMode.EDIT;
+        final CharMappingItem entity = new AdminDictMappingAction().createCharMappingItem(body, () -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToUpdateCrudTable(GLOBAL, String.valueOf(body.id)));
+            return null;
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(body.id)));
+            return null;
+        });
+        charMappingService.store(body.dictId, entity);
+        return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(entity.getId())).created(false).status(ApiResult.Status.OK)
+                .result());
+    }
+
+    // DELETE /api/admin/dict/mapping/setting/{dictId}/{id}
+    @Execute
+    public JsonResponse<ApiResult> delete$setting(final String dictId, final long id) {
+        charMappingService.getCharMappingItem(dictId, id).ifPresent(entity -> {
+            charMappingService.delete(dictId, entity);
+            saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
+        }).orElse(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(id)));
+        });
+        return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(id)).created(false).status(ApiResult.Status.OK).result());
+    }
+
+    // POST /api/admin/dict/mapping/upload/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> post$upload(final String dictId, final UploadForm form) {
+        form.dictId = dictId;
+        validateApi(form, messages -> {});
+        final CharMappingFile file = charMappingService.getCharMappingFile(form.dictId).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL));
+            return null;
+        });
+        try (InputStream inputStream = form.charMappingFile.getInputStream()) {
+            file.update(inputStream);
+        } catch (final Throwable e) {
+            e.printStackTrace();
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL));
+        }
+        return asJson(new ApiResult.ApiResponse().status(ApiResult.Status.OK).result());
+    }
+
+    // GET /api/admin/dict/mapping/download/{dictId}
+    @Execute
+    public StreamResponse get$download(final String dictId, final DownloadBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        return charMappingService.getCharMappingFile(body.dictId).map(file -> {
+            return asStream(new File(file.getPath()).getName()).contentTypeOctetStream().stream(out -> {
+                try (InputStream inputStream = file.getInputStream()) {
+                    out.write(inputStream);
+                }
+            });
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToDownloadProtwordsFile(GLOBAL));
+            return null;
+        });
+    }
+
+    protected EditBody createEditBody(final CharMappingItem entity, final String dictId) {
+        final EditBody body = new EditBody();
+        body.id = entity.getId();
+        body.dictId = dictId;
+        body.inputs = entity.getInputsValue();
+        body.output = entity.getOutput();
+        return body;
+    }
+}

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/mapping/CreateBody.java

@@ -0,0 +1,6 @@
+package org.codelibs.fess.app.web.api.admin.dict.mapping;
+
+import org.codelibs.fess.app.web.admin.dict.mapping.CreateForm;
+
+public class CreateBody extends CreateForm {
+}

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/mapping/DownloadBody.java

@@ -0,0 +1,6 @@
+package org.codelibs.fess.app.web.api.admin.dict.mapping;
+
+import org.codelibs.fess.app.web.admin.dict.mapping.DownloadForm;
+
+public class DownloadBody extends DownloadForm {
+}

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/mapping/EditBody.java

@@ -0,0 +1,6 @@
+package org.codelibs.fess.app.web.api.admin.dict.mapping;
+
+import org.codelibs.fess.app.web.admin.dict.mapping.EditForm;
+
+public class EditBody extends EditForm {
+}

+ 7 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/mapping/SearchBody.java

@@ -0,0 +1,7 @@
+package org.codelibs.fess.app.web.api.admin.dict.mapping;
+
+import org.codelibs.fess.app.web.admin.dict.mapping.SearchForm;
+
+public class SearchBody extends SearchForm {
+    public Integer pageNumber;
+}

+ 141 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/protwords/ApiAdminDictProtwordsAction.java

@@ -0,0 +1,141 @@
+package org.codelibs.fess.app.web.api.admin.dict.protwords;
+
+import org.codelibs.fess.app.pager.ProtwordsPager;
+import org.codelibs.fess.app.service.ProtwordsService;
+import org.codelibs.fess.app.web.CrudMode;
+import org.codelibs.fess.app.web.admin.dict.protwords.UploadForm;
+import org.codelibs.fess.app.web.api.ApiResult;
+import org.codelibs.fess.app.web.api.admin.FessApiAdminAction;
+import org.codelibs.fess.dict.protwords.ProtwordsFile;
+import org.codelibs.fess.dict.protwords.ProtwordsItem;
+import org.lastaflute.web.Execute;
+import org.lastaflute.web.response.JsonResponse;
+import org.lastaflute.web.response.StreamResponse;
+
+import javax.annotation.Resource;
+
+import java.io.*;
+import java.util.stream.Collectors;
+import static org.codelibs.fess.app.web.admin.dict.protwords.AdminDictProtwordsAction.*;
+
+public class ApiAdminDictProtwordsAction extends FessApiAdminAction {
+    @Resource
+    private ProtwordsService protwordsService;
+
+    // GET /api/admin/dict/protwords/settings/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> get$settings(final String dictId, final SearchBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        final ProtwordsPager pager = new ProtwordsPager();
+        if (body.pageNumber != null) {
+            pager.setCurrentPageNumber(body.pageNumber);
+        }
+        return asJson(new ApiResult.ApiConfigsResponse<EditBody>()
+                .settings(
+                        protwordsService.getProtwordsList(body.dictId, pager).stream()
+                                .map(protwordsItem -> createEditBody(protwordsItem, dictId)).collect(Collectors.toList()))
+                .status(ApiResult.Status.OK).result());
+    }
+
+    // GET /api/admin/dict/protwords/setting/{dictId}/{id}
+    @Execute
+    public JsonResponse<ApiResult> get$setting(final String dictId, final long id) {
+        return asJson(new ApiResult.ApiConfigResponse()
+                .setting(protwordsService.getProtwordsItem(dictId, id).map(entity -> createEditBody(entity, dictId)).orElseGet(() -> {
+                    throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(id)));
+                    return null;
+                })).status(ApiResult.Status.OK).result());
+    }
+
+    // PUT /api/admin/dict/protwords/setting/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> put$setting(final String dictId, final CreateBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        body.crudMode = CrudMode.CREATE;
+        final ProtwordsItem entity = createProtwordsItem(body, () -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL));
+            return null;
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL));
+            return null;
+        });
+        protwordsService.store(body.dictId, entity);
+        return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(entity.getId())).created(true).status(ApiResult.Status.OK)
+                .result());
+    }
+
+    // POST /api/admin/dict/protwords/setting/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> post$setting(final String dictId, final EditBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        body.crudMode = CrudMode.EDIT;
+        final ProtwordsItem entity = createProtwordsItem(body, () -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToUpdateCrudTable(GLOBAL, String.valueOf(body.id)));
+            return null;
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(body.id)));
+            return null;
+        });
+        protwordsService.store(body.dictId, entity);
+        return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(entity.getId())).created(false).status(ApiResult.Status.OK)
+                .result());
+    }
+
+    // DELETE /api/admin/dict/protwords/setting/{dictId}/{id}
+    @Execute
+    public JsonResponse<ApiResult> delete$setting(final String dictId, final long id) {
+        protwordsService.getProtwordsItem(dictId, id).ifPresent(entity -> {
+            protwordsService.delete(dictId, entity);
+            saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
+        }).orElse(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(id)));
+        });
+        return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(id)).created(false).status(ApiResult.Status.OK).result());
+    }
+
+    // POST /api/admin/dict/protwords/upload/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> post$upload(final String dictId, final UploadForm form) {
+        form.dictId = dictId;
+        validateApi(form, messages -> {});
+        final ProtwordsFile file = protwordsService.getProtwordsFile(form.dictId).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL));
+            return null;
+        });
+        try (InputStream inputStream = form.protwordsFile.getInputStream()) {
+            file.update(inputStream);
+        } catch (final Throwable e) {
+            e.printStackTrace();
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL));
+        }
+        return asJson(new ApiResult.ApiResponse().status(ApiResult.Status.OK).result());
+    }
+
+    // GET /api/admin/dict/protwords/download/{dictId}
+    @Execute
+    public StreamResponse get$download(final String dictId, final DownloadBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        return protwordsService.getProtwordsFile(body.dictId).map(file -> {
+            return asStream(new File(file.getPath()).getName()).contentTypeOctetStream().stream(out -> {
+                try (InputStream inputStream = file.getInputStream()) {
+                    out.write(inputStream);
+                }
+            });
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToDownloadProtwordsFile(GLOBAL));
+            return null;
+        });
+    }
+
+    protected EditBody createEditBody(final ProtwordsItem entity, final String dictId) {
+        final EditBody body = new EditBody();
+        body.id = entity.getId();
+        body.dictId = dictId;
+        body.input = entity.getInputValue();
+        return body;
+    }
+}

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/protwords/CreateBody.java

@@ -0,0 +1,6 @@
+package org.codelibs.fess.app.web.api.admin.dict.protwords;
+
+import org.codelibs.fess.app.web.admin.dict.protwords.CreateForm;
+
+public class CreateBody extends CreateForm {
+}

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/protwords/DownloadBody.java

@@ -0,0 +1,6 @@
+package org.codelibs.fess.app.web.api.admin.dict.protwords;
+
+import org.codelibs.fess.app.web.admin.dict.protwords.DownloadForm;
+
+public class DownloadBody extends DownloadForm {
+}

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/protwords/EditBody.java

@@ -0,0 +1,6 @@
+package org.codelibs.fess.app.web.api.admin.dict.protwords;
+
+import org.codelibs.fess.app.web.admin.dict.protwords.EditForm;
+
+public class EditBody extends EditForm {
+}

+ 7 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/protwords/SearchBody.java

@@ -0,0 +1,7 @@
+package org.codelibs.fess.app.web.api.admin.dict.protwords;
+
+import org.codelibs.fess.app.web.admin.dict.protwords.SearchForm;
+
+public class SearchBody extends SearchForm {
+    public Integer pageNumber;
+}

+ 142 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/seunjeon/ApiAdminDictSeunjeonAction.java

@@ -0,0 +1,142 @@
+package org.codelibs.fess.app.web.api.admin.dict.seunjeon;
+
+import org.codelibs.fess.app.pager.SeunjeonPager;
+import org.codelibs.fess.app.service.SeunjeonService;
+import org.codelibs.fess.app.web.CrudMode;
+import org.codelibs.fess.app.web.admin.dict.seunjeon.UploadForm;
+import org.codelibs.fess.app.web.admin.dict.seunjeon.AdminDictSeunjeonAction;
+import org.codelibs.fess.app.web.api.ApiResult;
+import org.codelibs.fess.app.web.api.admin.FessApiAdminAction;
+import org.codelibs.fess.dict.seunjeon.SeunjeonFile;
+import org.codelibs.fess.dict.seunjeon.SeunjeonItem;
+import org.lastaflute.web.Execute;
+import org.lastaflute.web.response.JsonResponse;
+import org.lastaflute.web.response.StreamResponse;
+
+import javax.annotation.Resource;
+
+import java.io.*;
+import java.util.stream.Collectors;
+
+public class ApiAdminDictSeunjeonAction extends FessApiAdminAction {
+
+    @Resource
+    private SeunjeonService seunjeonService;
+
+    // GET /api/admin/dict/seunjeon/settings/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> get$settings(final String dictId, final SearchBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        SeunjeonPager pager = new SeunjeonPager();
+        if (body.pageNumber != null) {
+            pager.setCurrentPageNumber(body.pageNumber);
+        }
+        return asJson(new ApiResult.ApiConfigsResponse<EditBody>()
+                .settings(
+                        seunjeonService.getSeunjeonList(body.dictId, pager).stream()
+                                .map(protwordsItem -> createEditBody(protwordsItem, dictId)).collect(Collectors.toList()))
+                .status(ApiResult.Status.OK).result());
+    }
+
+    // GET /api/admin/dict/seunjeon/setting/{dictId}/{id}
+    @Execute
+    public JsonResponse<ApiResult> get$setting(final String dictId, final long id) {
+        return asJson(new ApiResult.ApiConfigResponse()
+                .setting(seunjeonService.getSeunjeonItem(dictId, id).map(entity -> createEditBody(entity, dictId)).orElseGet(() -> {
+                    throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(id)));
+                    return null;
+                })).status(ApiResult.Status.OK).result());
+    }
+
+    // PUT /api/admin/dict/seunjeon/setting/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> put$setting(final String dictId, final CreateBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        body.crudMode = CrudMode.CREATE;
+        final SeunjeonItem entity = new AdminDictSeunjeonAction().createSeunjeonItem(body, () -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL));
+            return null;
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL));
+            return null;
+        });
+        seunjeonService.store(body.dictId, entity);
+        return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(entity.getId())).created(true).status(ApiResult.Status.OK)
+                .result());
+    }
+
+    // POST /api/admin/dict/seunjeon/setting/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> post$setting(final String dictId, final EditBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        body.crudMode = CrudMode.EDIT;
+        final SeunjeonItem entity = new AdminDictSeunjeonAction().createSeunjeonItem(body, () -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToUpdateCrudTable(GLOBAL, String.valueOf(body.id)));
+            return null;
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(body.id)));
+            return null;
+        });
+        seunjeonService.store(body.dictId, entity);
+        return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(entity.getId())).created(false).status(ApiResult.Status.OK)
+                .result());
+    }
+
+    // DELETE /api/admin/dict/seunjeon/setting/{dictId}/{id}
+    @Execute
+    public JsonResponse<ApiResult> delete$setting(final String dictId, final long id) {
+        seunjeonService.getSeunjeonItem(dictId, id).ifPresent(entity -> {
+            seunjeonService.delete(dictId, entity);
+            saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
+        }).orElse(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(id)));
+        });
+        return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(id)).created(false).status(ApiResult.Status.OK).result());
+    }
+
+    // POST /api/admin/dict/seunjeon/upload/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> post$upload(final String dictId, final UploadForm form) {
+        form.dictId = dictId;
+        validateApi(form, messages -> {});
+        final SeunjeonFile file = seunjeonService.getSeunjeonFile(form.dictId).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL));
+            return null;
+        });
+        try (InputStream inputStream = form.seunjeonFile.getInputStream()) {
+            file.update(inputStream);
+        } catch (final Throwable e) {
+            e.printStackTrace();
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL));
+        }
+        return asJson(new ApiResult.ApiResponse().status(ApiResult.Status.OK).result());
+    }
+
+    // GET /api/admin/dict/seunjeon/download/{dictId}
+    @Execute
+    public StreamResponse get$download(final String dictId, final DownloadBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        return seunjeonService.getSeunjeonFile(body.dictId).map(file -> {
+            return asStream(new File(file.getPath()).getName()).contentTypeOctetStream().stream(out -> {
+                try (InputStream inputStream = file.getInputStream()) {
+                    out.write(inputStream);
+                }
+            });
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToDownloadProtwordsFile(GLOBAL));
+            return null;
+        });
+    }
+
+    protected EditBody createEditBody(final SeunjeonItem entity, final String dictId) {
+        final EditBody body = new EditBody();
+        body.id = entity.getId();
+        body.dictId = dictId;
+        body.inputs = entity.getInputsValue();
+        return body;
+    }
+}

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/seunjeon/CreateBody.java

@@ -0,0 +1,6 @@
+package org.codelibs.fess.app.web.api.admin.dict.seunjeon;
+
+import org.codelibs.fess.app.web.admin.dict.seunjeon.CreateForm;
+
+public class CreateBody extends CreateForm {
+}

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/seunjeon/DownloadBody.java

@@ -0,0 +1,6 @@
+package org.codelibs.fess.app.web.api.admin.dict.seunjeon;
+
+import org.codelibs.fess.app.web.admin.dict.seunjeon.DownloadForm;
+
+public class DownloadBody extends DownloadForm {
+}

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/seunjeon/EditBody.java

@@ -0,0 +1,6 @@
+package org.codelibs.fess.app.web.api.admin.dict.seunjeon;
+
+import org.codelibs.fess.app.web.admin.dict.seunjeon.EditForm;
+
+public class EditBody extends EditForm {
+}

+ 7 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/seunjeon/SearchBody.java

@@ -0,0 +1,7 @@
+package org.codelibs.fess.app.web.api.admin.dict.seunjeon;
+
+import org.codelibs.fess.app.web.admin.dict.seunjeon.SearchForm;
+
+public class SearchBody extends SearchForm {
+    public Integer pageNumber;
+}

+ 143 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/synonym/ApiAdminDictSynonymAction.java

@@ -0,0 +1,143 @@
+package org.codelibs.fess.app.web.api.admin.dict.synonym;
+
+import org.codelibs.fess.app.pager.SynonymPager;
+import org.codelibs.fess.app.service.SynonymService;
+import org.codelibs.fess.app.web.CrudMode;
+import org.codelibs.fess.app.web.admin.dict.synonym.AdminDictSynonymAction;
+import org.codelibs.fess.app.web.admin.dict.synonym.UploadForm;
+import org.codelibs.fess.app.web.api.ApiResult;
+import org.codelibs.fess.app.web.api.admin.FessApiAdminAction;
+import org.codelibs.fess.dict.synonym.SynonymFile;
+import org.codelibs.fess.dict.synonym.SynonymItem;
+import org.lastaflute.web.Execute;
+import org.lastaflute.web.response.JsonResponse;
+import org.lastaflute.web.response.StreamResponse;
+
+import javax.annotation.Resource;
+
+import java.io.*;
+import java.util.stream.Collectors;
+
+public class ApiAdminDictSynonymAction extends FessApiAdminAction {
+
+    @Resource
+    private SynonymService synonymService;
+
+    // GET /api/admin/dict/synonym/settings/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> get$settings(final String dictId, final SearchBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        SynonymPager pager = new SynonymPager();
+        if (body.pageNumber != null) {
+            pager.setCurrentPageNumber(body.pageNumber);
+        }
+        return asJson(new ApiResult.ApiConfigsResponse<EditBody>()
+                .settings(
+                        synonymService.getSynonymList(body.dictId, pager).stream()
+                                .map(protwordsItem -> createEditBody(protwordsItem, dictId)).collect(Collectors.toList()))
+                .status(ApiResult.Status.OK).result());
+    }
+
+    // GET /api/admin/dict/synonym/setting/{dictId}/{id}
+    @Execute
+    public JsonResponse<ApiResult> get$setting(final String dictId, final long id) {
+        return asJson(new ApiResult.ApiConfigResponse()
+                .setting(synonymService.getSynonymItem(dictId, id).map(entity -> createEditBody(entity, dictId)).orElseGet(() -> {
+                    throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(id)));
+                    return null;
+                })).status(ApiResult.Status.OK).result());
+    }
+
+    // PUT /api/admin/dict/synonym/setting/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> put$setting(final String dictId, final CreateBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        body.crudMode = CrudMode.CREATE;
+        final SynonymItem entity = new AdminDictSynonymAction().createSynonymItem(body, () -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL));
+            return null;
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL));
+            return null;
+        });
+        synonymService.store(body.dictId, entity);
+        return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(entity.getId())).created(true).status(ApiResult.Status.OK)
+                .result());
+    }
+
+    // POST /api/admin/dict/synonym/setting/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> post$setting(final String dictId, final EditBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        body.crudMode = CrudMode.EDIT;
+        final SynonymItem entity = new AdminDictSynonymAction().createSynonymItem(body, () -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToUpdateCrudTable(GLOBAL, String.valueOf(body.id)));
+            return null;
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(body.id)));
+            return null;
+        });
+        synonymService.store(body.dictId, entity);
+        return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(entity.getId())).created(false).status(ApiResult.Status.OK)
+                .result());
+    }
+
+    // DELETE /api/admin/dict/synonym/setting/{dictId}/{id}
+    @Execute
+    public JsonResponse<ApiResult> delete$setting(final String dictId, final long id) {
+        synonymService.getSynonymItem(dictId, id).ifPresent(entity -> {
+            synonymService.delete(dictId, entity);
+            saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
+        }).orElse(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(id)));
+        });
+        return asJson(new ApiResult.ApiUpdateResponse().id(String.valueOf(id)).created(false).status(ApiResult.Status.OK).result());
+    }
+
+    // POST /api/admin/dict/synonym/upload/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> post$upload(final String dictId, final UploadForm form) {
+        form.dictId = dictId;
+        validateApi(form, messages -> {});
+        final SynonymFile file = synonymService.getSynonymFile(form.dictId).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL));
+            return null;
+        });
+        try (InputStream inputStream = form.synonymFile.getInputStream()) {
+            file.update(inputStream);
+        } catch (final Throwable e) {
+            e.printStackTrace();
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL));
+        }
+        return asJson(new ApiResult.ApiResponse().status(ApiResult.Status.OK).result());
+    }
+
+    // GET /api/admin/dict/synonym/download/{dictId}
+    @Execute
+    public StreamResponse get$download(final String dictId, final DownloadBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        return synonymService.getSynonymFile(body.dictId).map(file -> {
+            return asStream(new File(file.getPath()).getName()).contentTypeOctetStream().stream(out -> {
+                try (InputStream inputStream = file.getInputStream()) {
+                    out.write(inputStream);
+                }
+            });
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToDownloadProtwordsFile(GLOBAL));
+            return null;
+        });
+    }
+
+    protected EditBody createEditBody(final SynonymItem entity, final String dictId) {
+        final EditBody body = new EditBody();
+        body.id = entity.getId();
+        body.dictId = dictId;
+        body.inputs = entity.getInputsValue();
+        body.outputs = entity.getOutputsValue();
+        return body;
+    }
+}

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/synonym/CreateBody.java

@@ -0,0 +1,6 @@
+package org.codelibs.fess.app.web.api.admin.dict.synonym;
+
+import org.codelibs.fess.app.web.admin.dict.synonym.CreateForm;
+
+public class CreateBody extends CreateForm {
+}

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/synonym/DownloadBody.java

@@ -0,0 +1,6 @@
+package org.codelibs.fess.app.web.api.admin.dict.synonym;
+
+import org.codelibs.fess.app.web.admin.dict.synonym.DownloadForm;
+
+public class DownloadBody extends DownloadForm {
+}

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/synonym/EditBody.java

@@ -0,0 +1,6 @@
+package org.codelibs.fess.app.web.api.admin.dict.synonym;
+
+import org.codelibs.fess.app.web.admin.dict.synonym.EditForm;
+
+public class EditBody extends EditForm {
+}

+ 7 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/synonym/SearchBody.java

@@ -0,0 +1,7 @@
+package org.codelibs.fess.app.web.api.admin.dict.synonym;
+
+import org.codelibs.fess.app.web.admin.dict.synonym.SearchForm;
+
+public class SearchBody extends SearchForm {
+    public Integer pageNumber;
+}