浏览代码

add stopwords dictionary manager

Kazushi Morishima 7 年之前
父节点
当前提交
2c837f93c3
共有 40 个文件被更改,包括 2654 次插入397 次删除
  1. 123 0
      src/main/java/org/codelibs/fess/app/pager/StopwordsPager.java
  2. 80 0
      src/main/java/org/codelibs/fess/app/service/StopwordsService.java
  3. 382 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/stopwords/AdminDictStopwordsAction.java
  4. 42 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/stopwords/CreateForm.java
  5. 23 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/stopwords/DownloadForm.java
  6. 33 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/stopwords/EditForm.java
  7. 27 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/stopwords/SearchForm.java
  8. 32 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/stopwords/UploadForm.java
  9. 155 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/stopwords/ApiAdminDictStopwordsAction.java
  10. 21 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/stopwords/CreateBody.java
  11. 21 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/stopwords/DownloadBody.java
  12. 21 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/stopwords/EditBody.java
  13. 21 0
      src/main/java/org/codelibs/fess/app/web/api/admin/dict/stopwords/SearchBody.java
  14. 2 2
      src/main/java/org/codelibs/fess/dict/protwords/ProtwordsFile.java
  15. 39 0
      src/main/java/org/codelibs/fess/dict/stopwords/StopwordsCreator.java
  16. 292 0
      src/main/java/org/codelibs/fess/dict/stopwords/StopwordsFile.java
  17. 103 0
      src/main/java/org/codelibs/fess/dict/stopwords/StopwordsItem.java
  18. 17 4
      src/main/java/org/codelibs/fess/mylasta/action/FessHtmlPath.java
  19. 45 7
      src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java
  20. 35 0
      src/main/java/org/codelibs/fess/mylasta/action/FessMessages.java
  21. 306 321
      src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java
  22. 0 28
      src/main/resources/esclient.xml
  23. 1 0
      src/main/resources/fess_config.properties
  24. 6 0
      src/main/resources/fess_dict.xml
  25. 20 2
      src/main/resources/fess_indices/fess.json
  26. 0 0
      src/main/resources/fess_indices/fess/ja/stopwords.txt
  27. 0 0
      src/main/resources/fess_indices/fess/ko/stopwords.txt
  28. 18 5
      src/main/resources/fess_label.properties
  29. 14 1
      src/main/resources/fess_label_de.properties
  30. 18 5
      src/main/resources/fess_label_en.properties
  31. 29 16
      src/main/resources/fess_label_ja.properties
  32. 15 2
      src/main/resources/fess_label_ko.properties
  33. 17 4
      src/main/resources/fess_label_ru.properties
  34. 2 0
      src/main/resources/fess_message.properties
  35. 154 0
      src/main/webapp/WEB-INF/view/admin/dict/stopwords/admin_dict_stopwords.jsp
  36. 123 0
      src/main/webapp/WEB-INF/view/admin/dict/stopwords/admin_dict_stopwords_details.jsp
  37. 103 0
      src/main/webapp/WEB-INF/view/admin/dict/stopwords/admin_dict_stopwords_download.jsp
  38. 120 0
      src/main/webapp/WEB-INF/view/admin/dict/stopwords/admin_dict_stopwords_edit.jsp
  39. 107 0
      src/main/webapp/WEB-INF/view/admin/dict/stopwords/admin_dict_stopwords_upload.jsp
  40. 87 0
      src/test/java/org/codelibs/fess/it/admin/dict/StopwordsTests.java

+ 123 - 0
src/main/java/org/codelibs/fess/app/pager/StopwordsPager.java

@@ -0,0 +1,123 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.pager;
+
+import java.io.Serializable;
+import java.util.List;
+
+import org.codelibs.fess.util.ComponentUtil;
+
+public class StopwordsPager implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private int allRecordCount;
+
+    private int allPageCount;
+
+    private boolean existPrePage;
+
+    private boolean existNextPage;
+
+    private List<Integer> pageNumberList;
+
+    private int pageSize;
+
+    private int currentPageNumber;
+
+    public String id;
+
+    public void clear() {
+        allRecordCount = 0;
+        allPageCount = 0;
+        existPrePage = false;
+        existNextPage = false;
+        pageSize = getDefaultPageSize();
+        currentPageNumber = getDefaultCurrentPageNumber();
+
+        id = null;
+    }
+
+    protected int getDefaultPageSize() {
+        return ComponentUtil.getFessConfig().getPagingPageSizeAsInteger();
+    }
+
+    protected int getDefaultCurrentPageNumber() {
+        return 1;
+    }
+
+    public int getAllRecordCount() {
+        return allRecordCount;
+    }
+
+    public void setAllRecordCount(final int allRecordCount) {
+        this.allRecordCount = allRecordCount;
+    }
+
+    public int getAllPageCount() {
+        return allPageCount;
+    }
+
+    public void setAllPageCount(final int allPageCount) {
+        this.allPageCount = allPageCount;
+    }
+
+    public boolean isExistPrePage() {
+        return existPrePage;
+    }
+
+    public void setExistPrePage(final boolean existPrePage) {
+        this.existPrePage = existPrePage;
+    }
+
+    public boolean isExistNextPage() {
+        return existNextPage;
+    }
+
+    public void setExistNextPage(final boolean existNextPage) {
+        this.existNextPage = existNextPage;
+    }
+
+    public int getPageSize() {
+        if (pageSize <= 0) {
+            pageSize = getDefaultPageSize();
+        }
+        return pageSize;
+    }
+
+    public void setPageSize(final int pageSize) {
+        this.pageSize = pageSize;
+    }
+
+    public int getCurrentPageNumber() {
+        if (currentPageNumber <= 0) {
+            currentPageNumber = getDefaultCurrentPageNumber();
+        }
+        return currentPageNumber;
+    }
+
+    public void setCurrentPageNumber(final int currentPageNumber) {
+        this.currentPageNumber = currentPageNumber;
+    }
+
+    public List<Integer> getPageNumberList() {
+        return pageNumberList;
+    }
+
+    public void setPageNumberList(final List<Integer> pageNumberList) {
+        this.pageNumberList = pageNumberList;
+    }
+}

+ 80 - 0
src/main/java/org/codelibs/fess/app/service/StopwordsService.java

@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.service;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Resource;
+
+import org.codelibs.core.beans.util.BeanUtil;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.app.pager.StopwordsPager;
+import org.codelibs.fess.dict.DictionaryFile.PagingList;
+import org.codelibs.fess.dict.DictionaryManager;
+import org.codelibs.fess.dict.stopwords.StopwordsFile;
+import org.codelibs.fess.dict.stopwords.StopwordsItem;
+import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.dbflute.optional.OptionalEntity;
+
+public class StopwordsService {
+    @Resource
+    protected DictionaryManager dictionaryManager;
+
+    @Resource
+    protected FessConfig fessConfig;
+
+    public List<StopwordsItem> getStopwordsList(final String dictId, final StopwordsPager stopwordsPager) {
+        return getStopwordsFile(dictId).map(
+                file -> {
+                    final int pageSize = stopwordsPager.getPageSize();
+                    final PagingList<StopwordsItem> stopwordsList =
+                            file.selectList((stopwordsPager.getCurrentPageNumber() - 1) * pageSize, pageSize);
+
+                    // update pager
+                    BeanUtil.copyBeanToBean(stopwordsList, stopwordsPager, option -> option.include(Constants.PAGER_CONVERSION_RULE));
+                    stopwordsList.setPageRangeSize(fessConfig.getPagingPageRangeSizeAsInteger());
+                    stopwordsPager.setPageNumberList(stopwordsList.createPageNumberList());
+
+                    return (List<StopwordsItem>) stopwordsList;
+                }).orElse(Collections.emptyList());
+    }
+
+    public OptionalEntity<StopwordsFile> getStopwordsFile(final String dictId) {
+        return dictionaryManager.getDictionaryFile(dictId).filter(file -> file instanceof StopwordsFile)
+                .map(file -> OptionalEntity.of((StopwordsFile) file)).orElse(OptionalEntity.empty());
+    }
+
+    public OptionalEntity<StopwordsItem> getStopwordsItem(final String dictId, final long id) {
+        return getStopwordsFile(dictId).map(file -> file.get(id).get());
+    }
+
+    public void store(final String dictId, final StopwordsItem stopwordsItem) {
+        getStopwordsFile(dictId).ifPresent(file -> {
+            if (stopwordsItem.getId() == 0) {
+                file.insert(stopwordsItem);
+            } else {
+                file.update(stopwordsItem);
+            }
+        });
+    }
+
+    public void delete(final String dictId, final StopwordsItem stopwordsItem) {
+        getStopwordsFile(dictId).ifPresent(file -> {
+            file.delete(stopwordsItem);
+        });
+    }
+}

+ 382 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/stopwords/AdminDictStopwordsAction.java

@@ -0,0 +1,382 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.stopwords;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.annotation.Resource;
+
+import org.codelibs.core.beans.util.BeanUtil;
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.app.pager.StopwordsPager;
+import org.codelibs.fess.app.service.StopwordsService;
+import org.codelibs.fess.app.web.CrudMode;
+import org.codelibs.fess.app.web.admin.dict.AdminDictAction;
+import org.codelibs.fess.app.web.base.FessAdminAction;
+import org.codelibs.fess.dict.stopwords.StopwordsItem;
+import org.codelibs.fess.util.ComponentUtil;
+import org.codelibs.fess.util.RenderDataUtil;
+import org.dbflute.optional.OptionalEntity;
+import org.dbflute.optional.OptionalThing;
+import org.lastaflute.web.Execute;
+import org.lastaflute.web.response.ActionResponse;
+import org.lastaflute.web.response.HtmlResponse;
+import org.lastaflute.web.response.render.RenderData;
+import org.lastaflute.web.ruts.process.ActionRuntime;
+import org.lastaflute.web.validation.VaErrorHook;
+
+/**
+ * @author ma2tani
+ */
+public class AdminDictStopwordsAction extends FessAdminAction {
+
+    // ===================================================================================
+    //                                                                           Attribute
+    //                                                                           =========
+    @Resource
+    private StopwordsService stopwordsService;
+    @Resource
+    private StopwordsPager stopwordsPager;
+
+    // ===================================================================================
+    //                                                                               Hook
+    //                                                                              ======
+    @Override
+    protected void setupHtmlData(final ActionRuntime runtime) {
+        super.setupHtmlData(runtime);
+        runtime.registerData("helpLink", systemHelper.getHelpLink(fessConfig.getOnlineHelpNameDictStopwords()));
+    }
+
+    // ===================================================================================
+    //                                                                      Search Execute
+    //                                                                      ==============
+    @Execute
+    public HtmlResponse index(final SearchForm form) {
+        validate(form, messages -> {}, () -> asDictIndexHtml());
+        return asHtml(path_AdminDictStopwords_AdminDictStopwordsJsp).renderWith(data -> {
+            searchPaging(data, form);
+        });
+    }
+
+    @Execute
+    public HtmlResponse list(final OptionalThing<Integer> pageNumber, final SearchForm form) {
+        validate(form, messages -> {}, () -> asDictIndexHtml());
+        pageNumber.ifPresent(num -> {
+            stopwordsPager.setCurrentPageNumber(pageNumber.get());
+        }).orElse(() -> {
+            stopwordsPager.setCurrentPageNumber(0);
+        });
+        return asHtml(path_AdminDictStopwords_AdminDictStopwordsJsp).renderWith(data -> {
+            searchPaging(data, form);
+        });
+    }
+
+    @Execute
+    public HtmlResponse search(final SearchForm form) {
+        validate(form, messages -> {}, () -> asDictIndexHtml());
+        copyBeanToBean(form, stopwordsPager, op -> op.exclude(Constants.PAGER_CONVERSION_RULE));
+        return asHtml(path_AdminDictStopwords_AdminDictStopwordsJsp).renderWith(data -> {
+            searchPaging(data, form);
+        });
+    }
+
+    @Execute
+    public HtmlResponse reset(final SearchForm form) {
+        validate(form, messages -> {}, () -> asDictIndexHtml());
+        stopwordsPager.clear();
+        return asHtml(path_AdminDictStopwords_AdminDictStopwordsJsp).renderWith(data -> {
+            searchPaging(data, form);
+        });
+    }
+
+    protected void searchPaging(final RenderData data, final SearchForm form) {
+        // page navi
+        RenderDataUtil.register(data, "stopwordsItemItems", stopwordsService.getStopwordsList(form.dictId, stopwordsPager));
+
+        // restore from pager
+        BeanUtil.copyBeanToBean(stopwordsPager, form, op -> {
+            op.exclude(Constants.PAGER_CONVERSION_RULE);
+        });
+    }
+
+    // ===================================================================================
+    //                                                                        Edit Execute
+    //                                                                        ============
+    // -----------------------------------------------------
+    //                                            Entry Page
+    //                                            ----------
+    @Execute
+    public HtmlResponse createnew(final String dictId) {
+        saveToken();
+        return asHtml(path_AdminDictStopwords_AdminDictStopwordsEditJsp).useForm(CreateForm.class, op -> {
+            op.setup(form -> {
+                form.initialize();
+                form.crudMode = CrudMode.CREATE;
+                form.dictId = dictId;
+            });
+        });
+    }
+
+    @Execute
+    public HtmlResponse edit(final EditForm form) {
+        validate(form, messages -> {}, () -> asListHtml(form.dictId));
+        stopwordsService
+                .getStopwordsItem(form.dictId, form.id)
+                .ifPresent(entity -> {
+                    form.input = entity.getInputValue();
+                })
+                .orElse(() -> {
+                    throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.getDisplayId()),
+                            () -> asListHtml(form.dictId));
+                });
+        saveToken();
+        if (form.crudMode.intValue() == CrudMode.EDIT) {
+            // back
+            form.crudMode = CrudMode.DETAILS;
+            return asDetailsHtml();
+        } else {
+            form.crudMode = CrudMode.EDIT;
+            return asEditHtml();
+        }
+    }
+
+    // -----------------------------------------------------
+    //                                               Details
+    //                                               -------
+    @Execute
+    public HtmlResponse details(final String dictId, final int crudMode, final long id) {
+        verifyCrudMode(crudMode, CrudMode.DETAILS, dictId);
+        saveToken();
+        return asDetailsHtml().useForm(
+                EditForm.class,
+                op -> {
+                    op.setup(form -> {
+                        stopwordsService
+                                .getStopwordsItem(dictId, id)
+                                .ifPresent(entity -> {
+                                    form.input = entity.getInputValue();
+                                })
+                                .orElse(() -> {
+                                    throwValidationError(
+                                            messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, dictId + ":" + id),
+                                            () -> asListHtml(dictId));
+                                });
+                        form.id = id;
+                        form.crudMode = crudMode;
+                        form.dictId = dictId;
+                    });
+                });
+    }
+
+    // -----------------------------------------------------
+    //                                              Download
+    //                                               -------
+    @Execute
+    public HtmlResponse downloadpage(final String dictId) {
+        saveToken();
+        return asHtml(path_AdminDictStopwords_AdminDictStopwordsDownloadJsp).useForm(DownloadForm.class, op -> {
+            op.setup(form -> {
+                form.dictId = dictId;
+            });
+        }).renderWith(data -> {
+            stopwordsService.getStopwordsFile(dictId).ifPresent(file -> {
+                RenderDataUtil.register(data, "path", file.getPath());
+            }).orElse(() -> {
+                throwValidationError(messages -> messages.addErrorsFailedToDownloadStopwordsFile(GLOBAL), () -> asDictIndexHtml());
+            });
+        });
+    }
+
+    @Execute
+    public ActionResponse download(final DownloadForm form) {
+        validate(form, messages -> {}, () -> downloadpage(form.dictId));
+        verifyTokenKeep(() -> downloadpage(form.dictId));
+        return stopwordsService.getStopwordsFile(form.dictId).map(file -> {
+            return asStream(new File(file.getPath()).getName()).contentTypeOctetStream().stream(out -> {
+                try (InputStream inputStream = file.getInputStream()) {
+                    out.write(inputStream);
+                }
+            });
+        }).orElseGet(() -> {
+            throwValidationError(messages -> messages.addErrorsFailedToDownloadStopwordsFile(GLOBAL), () -> downloadpage(form.dictId));
+            return null;
+        });
+    }
+
+    // -----------------------------------------------------
+    //                                                Upload
+    //                                               -------
+    @Execute
+    public HtmlResponse uploadpage(final String dictId) {
+        saveToken();
+        return asHtml(path_AdminDictStopwords_AdminDictStopwordsUploadJsp).useForm(UploadForm.class, op -> {
+            op.setup(form -> {
+                form.dictId = dictId;
+            });
+        }).renderWith(data -> {
+            stopwordsService.getStopwordsFile(dictId).ifPresent(file -> {
+                RenderDataUtil.register(data, "path", file.getPath());
+            }).orElse(() -> {
+                throwValidationError(messages -> messages.addErrorsFailedToDownloadStopwordsFile(GLOBAL), () -> asDictIndexHtml());
+            });
+        });
+    }
+
+    @Execute
+    public HtmlResponse upload(final UploadForm form) {
+        validate(form, messages -> {}, () -> uploadpage(form.dictId));
+        verifyToken(() -> uploadpage(form.dictId));
+        return stopwordsService.getStopwordsFile(form.dictId).map(file -> {
+            try (InputStream inputStream = form.stopwordsFile.getInputStream()) {
+                file.update(inputStream);
+            } catch (final IOException e) {
+                throwValidationError(messages -> messages.addErrorsFailedToUploadStopwordsFile(GLOBAL), () -> {
+                    return redirectWith(getClass(), moreUrl("uploadpage/" + form.dictId));
+                });
+            }
+            saveInfo(messages -> messages.addSuccessUploadSynonymFile(GLOBAL));
+            return redirectWith(getClass(), moreUrl("uploadpage/" + form.dictId));
+        }).orElseGet(() -> {
+            throwValidationError(messages -> messages.addErrorsFailedToUploadStopwordsFile(GLOBAL), () -> uploadpage(form.dictId));
+            return null;
+        });
+
+    }
+
+    // -----------------------------------------------------
+    //                                         Actually Crud
+    //                                         -------------
+    @Execute
+    public HtmlResponse create(final CreateForm form) {
+        verifyCrudMode(form.crudMode, CrudMode.CREATE, form.dictId);
+        validate(form, messages -> {}, () -> asEditHtml());
+        verifyToken(() -> asEditHtml());
+        createStopwordsItem(form, () -> asEditHtml()).ifPresent(entity -> {
+            stopwordsService.store(form.dictId, entity);
+            saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL));
+        }).orElse(() -> throwValidationError(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL), () -> asEditHtml()));
+        return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
+    }
+
+    @Execute
+    public HtmlResponse update(final EditForm form) {
+        verifyCrudMode(form.crudMode, CrudMode.EDIT, form.dictId);
+        validate(form, messages -> {}, () -> asEditHtml());
+        verifyToken(() -> asEditHtml());
+        createStopwordsItem(form, () -> asEditHtml()).ifPresent(entity -> {
+            stopwordsService.store(form.dictId, entity);
+            saveInfo(messages -> messages.addSuccessCrudUpdateCrudTable(GLOBAL));
+        }).orElse(
+                () -> throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.getDisplayId()),
+                        () -> asEditHtml()));
+        return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
+    }
+
+    @Execute
+    public HtmlResponse delete(final EditForm form) {
+        verifyCrudMode(form.crudMode, CrudMode.DETAILS, form.dictId);
+        validate(form, messages -> {}, () -> asDetailsHtml());
+        verifyToken(() -> asDetailsHtml());
+        stopwordsService
+                .getStopwordsItem(form.dictId, form.id)
+                .ifPresent(entity -> {
+                    stopwordsService.delete(form.dictId, entity);
+                    saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
+                })
+                .orElse(() -> {
+                    throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.getDisplayId()),
+                            () -> asDetailsHtml());
+                });
+        return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
+    }
+
+    //===================================================================================
+    //                                                                        Assist Logic
+    //                                                                        ============
+
+    private static OptionalEntity<StopwordsItem> getEntity(final CreateForm form) {
+        switch (form.crudMode) {
+        case CrudMode.CREATE:
+            final StopwordsItem entity = new StopwordsItem(0, StringUtil.EMPTY);
+            return OptionalEntity.of(entity);
+        case CrudMode.EDIT:
+            if (form instanceof EditForm) {
+                return ComponentUtil.getComponent(StopwordsService.class).getStopwordsItem(form.dictId, ((EditForm) form).id);
+            }
+            break;
+        default:
+            break;
+        }
+        return OptionalEntity.empty();
+    }
+
+    public static OptionalEntity<StopwordsItem> createStopwordsItem(final CreateForm form, final VaErrorHook hook) {
+        return getEntity(form).map(entity -> {
+            final String newInput = form.input;
+            validateStopwordsString(newInput, "input", hook);
+            entity.setNewInput(newInput);
+            return entity;
+        });
+    }
+
+    // ===================================================================================
+    //                                                                        Small Helper
+    //                                                                        ============
+    protected void verifyCrudMode(final int crudMode, final int expectedMode, final String dictId) {
+        if (crudMode != expectedMode) {
+            throwValidationError(messages -> {
+                messages.addErrorsCrudInvalidMode(GLOBAL, String.valueOf(expectedMode), String.valueOf(crudMode));
+            }, () -> asListHtml(dictId));
+        }
+    }
+
+    private static void validateStopwordsString(final String values, final String propertyName, final VaErrorHook hook) {
+        if (values.length() == 0) {
+            return;
+        }
+        // TODO validation
+    }
+
+    // ===================================================================================
+    //                                                                              JSP
+    //                                                                           =========
+
+    protected HtmlResponse asDictIndexHtml() {
+        return redirect(AdminDictAction.class);
+    }
+
+    private HtmlResponse asListHtml(final String dictId) {
+        return asHtml(path_AdminDictStopwords_AdminDictStopwordsJsp).renderWith(data -> {
+            RenderDataUtil.register(data, "stopwordsItemItems", stopwordsService.getStopwordsList(dictId, stopwordsPager));
+        }).useForm(SearchForm.class, setup -> {
+            setup.setup(form -> {
+                copyBeanToBean(stopwordsPager, form, op -> op.include("id"));
+            });
+        });
+    }
+
+    private HtmlResponse asEditHtml() {
+        return asHtml(path_AdminDictStopwords_AdminDictStopwordsEditJsp);
+    }
+
+    private HtmlResponse asDetailsHtml() {
+        return asHtml(path_AdminDictStopwords_AdminDictStopwordsDetailsJsp);
+    }
+
+}

+ 42 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/stopwords/CreateForm.java

@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.stopwords;
+
+import javax.validation.constraints.Size;
+
+import org.codelibs.fess.app.web.CrudMode;
+import org.lastaflute.web.validation.Required;
+import org.lastaflute.web.validation.theme.conversion.ValidateTypeFailure;
+
+/**
+ * @author ma2tani
+ */
+public class CreateForm {
+
+    @Required
+    public String dictId;
+
+    @ValidateTypeFailure
+    public Integer crudMode;
+
+    @Required
+    @Size(max = 1000)
+    public String input;
+
+    public void initialize() {
+        crudMode = CrudMode.CREATE;
+    }
+}

+ 23 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/stopwords/DownloadForm.java

@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.stopwords;
+
+import org.lastaflute.web.validation.Required;
+
+public class DownloadForm {
+    @Required
+    public String dictId;
+}

+ 33 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/stopwords/EditForm.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.stopwords;
+
+import org.lastaflute.web.validation.Required;
+import org.lastaflute.web.validation.theme.conversion.ValidateTypeFailure;
+
+/**
+ * @author ma2tani
+ */
+public class EditForm extends CreateForm {
+
+    @Required
+    @ValidateTypeFailure
+    public Long id;
+
+    public String getDisplayId() {
+        return dictId + ":" + id;
+    }
+}

+ 27 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/stopwords/SearchForm.java

@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.stopwords;
+
+import org.lastaflute.web.validation.Required;
+
+/**
+ * @author ma2tani
+ */
+public class SearchForm {
+
+    @Required
+    public String dictId;
+}

+ 32 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/stopwords/UploadForm.java

@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.stopwords;
+
+import org.lastaflute.web.ruts.multipart.MultipartFormFile;
+import org.lastaflute.web.validation.Required;
+
+/**
+ * @author ma2tani
+ */
+public class UploadForm {
+
+    @Required
+    public String dictId;
+
+    @Required
+    public MultipartFormFile stopwordsFile;
+
+}

+ 155 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/dict/stopwords/ApiAdminDictStopwordsAction.java

@@ -0,0 +1,155 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.api.admin.dict.stopwords;
+
+import static org.codelibs.fess.app.web.admin.dict.stopwords.AdminDictStopwordsAction.createStopwordsItem;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.stream.Collectors;
+
+import javax.annotation.Resource;
+
+import org.codelibs.fess.app.pager.StopwordsPager;
+import org.codelibs.fess.app.service.StopwordsService;
+import org.codelibs.fess.app.web.CrudMode;
+import org.codelibs.fess.app.web.admin.dict.stopwords.UploadForm;
+import org.codelibs.fess.app.web.api.ApiResult;
+import org.codelibs.fess.app.web.api.admin.FessApiAdminAction;
+import org.codelibs.fess.dict.stopwords.StopwordsFile;
+import org.codelibs.fess.dict.stopwords.StopwordsItem;
+import org.lastaflute.web.Execute;
+import org.lastaflute.web.response.JsonResponse;
+import org.lastaflute.web.response.StreamResponse;
+
+public class ApiAdminDictStopwordsAction extends FessApiAdminAction {
+    @Resource
+    private StopwordsService stopwordsService;
+
+    // GET /api/admin/dict/stopwords/settings/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> get$settings(final String dictId, final SearchBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        final StopwordsPager pager = copyBeanToNewBean(body, StopwordsPager.class);
+        return asJson(new ApiResult.ApiConfigsResponse<EditBody>()
+                .settings(
+                        stopwordsService.getStopwordsList(body.dictId, pager).stream()
+                                .map(stopwordsItem -> createEditBody(stopwordsItem, dictId)).collect(Collectors.toList()))
+                .status(ApiResult.Status.OK).result());
+    }
+
+    // GET /api/admin/dict/stopwords/setting/{dictId}/{id}
+    @Execute
+    public JsonResponse<ApiResult> get$setting(final String dictId, final long id) {
+        return asJson(new ApiResult.ApiConfigResponse()
+                .setting(stopwordsService.getStopwordsItem(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/stopwords/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 StopwordsItem entity = createStopwordsItem(body, () -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL));
+            return null;
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL));
+            return null;
+        });
+        stopwordsService.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/stopwords/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 StopwordsItem entity = createStopwordsItem(body, () -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToUpdateCrudTable(GLOBAL, String.valueOf(body.id)));
+            return null;
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, String.valueOf(body.id)));
+            return null;
+        });
+        stopwordsService.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/stopwords/setting/{dictId}/{id}
+    @Execute
+    public JsonResponse<ApiResult> delete$setting(final String dictId, final long id) {
+        stopwordsService.getStopwordsItem(dictId, id).ifPresent(entity -> {
+            stopwordsService.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/stopwords/upload/{dictId}
+    @Execute
+    public JsonResponse<ApiResult> post$upload(final String dictId, final UploadForm form) {
+        form.dictId = dictId;
+        validateApi(form, messages -> {});
+        final StopwordsFile file = stopwordsService.getStopwordsFile(form.dictId).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToUploadStopwordsFile(GLOBAL));
+            return null;
+        });
+        try (InputStream inputStream = form.stopwordsFile.getInputStream()) {
+            file.update(inputStream);
+        } catch (final IOException e) {
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToUploadStopwordsFile(GLOBAL));
+        }
+        return asJson(new ApiResult.ApiResponse().status(ApiResult.Status.OK).result());
+    }
+
+    // GET /api/admin/dict/stopwords/download/{dictId}
+    @Execute
+    public StreamResponse get$download(final String dictId, final DownloadBody body) {
+        body.dictId = dictId;
+        validateApi(body, messages -> {});
+        return stopwordsService.getStopwordsFile(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.addErrorsFailedToDownloadStopwordsFile(GLOBAL));
+            return null;
+        });
+    }
+
+    protected EditBody createEditBody(final StopwordsItem entity, final String dictId) {
+        final EditBody body = new EditBody();
+        body.id = entity.getId();
+        body.dictId = dictId;
+        body.input = entity.getInputValue();
+        return body;
+    }
+}

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

@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.api.admin.dict.stopwords;
+
+import org.codelibs.fess.app.web.admin.dict.stopwords.CreateForm;
+
+public class CreateBody extends CreateForm {
+}

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

@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.api.admin.dict.stopwords;
+
+import org.codelibs.fess.app.web.admin.dict.stopwords.DownloadForm;
+
+public class DownloadBody extends DownloadForm {
+}

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

@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.api.admin.dict.stopwords;
+
+import org.codelibs.fess.app.web.admin.dict.stopwords.EditForm;
+
+public class EditBody extends EditForm {
+}

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

@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.api.admin.dict.stopwords;
+
+import org.codelibs.fess.app.web.api.admin.dict.BaseSearchDictBody;
+
+public class SearchBody extends BaseSearchDictBody {
+}

+ 2 - 2
src/main/java/org/codelibs/fess/dict/protwords/ProtwordsFile.java

@@ -191,7 +191,7 @@ public class ProtwordsFile extends DictionaryFile<ProtwordsItem> {
 
     @Override
     public String toString() {
-        return "SynonymFile [path=" + path + ", srotwordsItemList=" + protwordsItemList + ", id=" + id + "]";
+        return "SynonymFile [path=" + path + ", protwordsItemList=" + protwordsItemList + ", id=" + id + "]";
     }
 
     protected class SynonymUpdater implements Closeable {
@@ -289,4 +289,4 @@ public class ProtwordsFile extends DictionaryFile<ProtwordsItem> {
             }
         }
     }
-}
+}

+ 39 - 0
src/main/java/org/codelibs/fess/dict/stopwords/StopwordsCreator.java

@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.dict.stopwords;
+
+import java.util.Date;
+
+import org.codelibs.fess.dict.DictionaryCreator;
+import org.codelibs.fess.dict.DictionaryFile;
+import org.codelibs.fess.dict.DictionaryItem;
+
+public class StopwordsCreator extends DictionaryCreator {
+
+    public StopwordsCreator() {
+        super("stopwords.*\\.txt");
+    }
+
+    public StopwordsCreator(final String pattern) {
+        super(pattern);
+    }
+
+    @Override
+    protected DictionaryFile<? extends DictionaryItem> newDictionaryFile(final String id, final String path, final Date timestamp) {
+        return new StopwordsFile(id, path, timestamp).manager(dictionaryManager);
+    }
+
+}

+ 292 - 0
src/main/java/org/codelibs/fess/dict/stopwords/StopwordsFile.java

@@ -0,0 +1,292 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.dict.stopwords;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import org.codelibs.core.io.CloseableUtil;
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.dict.DictionaryException;
+import org.codelibs.fess.dict.DictionaryFile;
+import org.dbflute.optional.OptionalEntity;
+
+public class StopwordsFile extends DictionaryFile<StopwordsItem> {
+    private static final String STOPWORDS = "stopwords";
+
+    List<StopwordsItem> stopwordsItemList;
+
+    public StopwordsFile(final String id, final String path, final Date timestamp) {
+        super(id, path, timestamp);
+    }
+
+    @Override
+    public String getType() {
+        return STOPWORDS;
+    }
+
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public synchronized OptionalEntity<StopwordsItem> get(final long id) {
+        if (stopwordsItemList == null) {
+            reload(null, null);
+        }
+
+        for (final StopwordsItem StopwordsItem : stopwordsItemList) {
+            if (id == StopwordsItem.getId()) {
+                return OptionalEntity.of(StopwordsItem);
+            }
+        }
+        return OptionalEntity.empty();
+    }
+
+    @Override
+    public synchronized PagingList<StopwordsItem> selectList(final int offset, final int size) {
+        if (stopwordsItemList == null) {
+            reload(null, null);
+        }
+
+        if (offset >= stopwordsItemList.size() || offset < 0) {
+            return new PagingList<>(Collections.<StopwordsItem> emptyList(), offset, size, stopwordsItemList.size());
+        }
+
+        int toIndex = offset + size;
+        if (toIndex > stopwordsItemList.size()) {
+            toIndex = stopwordsItemList.size();
+        }
+
+        return new PagingList<>(stopwordsItemList.subList(offset, toIndex), offset, size, stopwordsItemList.size());
+    }
+
+    @Override
+    public synchronized void insert(final StopwordsItem item) {
+        try (SynonymUpdater updater = new SynonymUpdater(item)) {
+            reload(updater, null);
+        }
+    }
+
+    @Override
+    public synchronized void update(final StopwordsItem item) {
+        try (SynonymUpdater updater = new SynonymUpdater(item)) {
+            reload(updater, null);
+        }
+    }
+
+    @Override
+    public synchronized void delete(final StopwordsItem item) {
+        final StopwordsItem StopwordsItem = item;
+        StopwordsItem.setNewInput(StringUtil.EMPTY);
+        try (SynonymUpdater updater = new SynonymUpdater(item)) {
+            reload(updater, null);
+        }
+    }
+
+    protected void reload(final SynonymUpdater updater, final InputStream in) {
+        final List<StopwordsItem> itemList = new ArrayList<>();
+        try (BufferedReader reader =
+                new BufferedReader(new InputStreamReader(in != null ? in : dictionaryManager.getContentInputStream(this), Constants.UTF_8))) {
+            long id = 0;
+            String line = null;
+            while ((line = reader.readLine()) != null) {
+                if (line.length() == 0 || line.charAt(0) == '#') {
+                    if (updater != null) {
+                        updater.write(line);
+                    }
+                    continue; // ignore empty lines and comments
+                }
+
+                final String inputStrings = line;
+                String input = null;
+                if (inputStrings != null) {
+                    input = unescape(inputStrings);
+                }
+
+                if (input.length() > 0) {
+                    id++;
+                    final StopwordsItem item = new StopwordsItem(id, input);
+                    if (updater != null) {
+                        final StopwordsItem newItem = updater.write(item);
+                        if (newItem != null) {
+                            itemList.add(newItem);
+                        } else {
+                            id--;
+                        }
+                    } else {
+                        itemList.add(item);
+                    }
+                }
+            }
+            if (updater != null) {
+                final StopwordsItem item = updater.commit();
+                if (item != null) {
+                    itemList.add(item);
+                }
+            }
+            stopwordsItemList = itemList;
+        } catch (final IOException e) {
+            throw new DictionaryException("Failed to parse " + path, e);
+        }
+    }
+
+    private String unescape(final String s) {
+        if (s.indexOf('\\') >= 0) {
+            final StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < s.length(); i++) {
+                final char ch = s.charAt(i);
+                if (ch == '\\' && i < s.length() - 1) {
+                    sb.append(s.charAt(++i));
+                } else {
+                    sb.append(ch);
+                }
+            }
+            return sb.toString();
+        }
+        return s;
+    }
+
+    public String getSimpleName() {
+        return new File(path).getName();
+    }
+
+    public InputStream getInputStream() throws IOException {
+        return new BufferedInputStream(dictionaryManager.getContentInputStream(this));
+    }
+
+    public synchronized void update(final InputStream in) throws IOException {
+        try (SynonymUpdater updater = new SynonymUpdater(null)) {
+            reload(updater, in);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "SynonymFile [path=" + path + ", stopwordsItemList=" + stopwordsItemList + ", id=" + id + "]";
+    }
+
+    protected class SynonymUpdater implements Closeable {
+
+        protected boolean isCommit = false;
+
+        protected File newFile;
+
+        protected Writer writer;
+
+        protected StopwordsItem item;
+
+        protected SynonymUpdater(final StopwordsItem newItem) {
+            try {
+                newFile = File.createTempFile(STOPWORDS, ".txt");
+                writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), Constants.UTF_8));
+            } catch (final IOException e) {
+                if (newFile != null) {
+                    newFile.delete();
+                }
+                throw new DictionaryException("Failed to write a userDict file.", e);
+            }
+            item = newItem;
+        }
+
+        public StopwordsItem write(final StopwordsItem oldItem) {
+            try {
+                if (item != null && item.getId() == oldItem.getId() && item.isUpdated()) {
+                    if (item.equals(oldItem)) {
+                        try {
+                            if (!item.isDeleted()) {
+                                // update
+                                writer.write(item.toLineString());
+                                writer.write(Constants.LINE_SEPARATOR);
+                                return new StopwordsItem(item.getId(), item.getNewInput());
+                            } else {
+                                return null;
+                            }
+                        } finally {
+                            item.setNewInput(null);
+                        }
+                    } else {
+                        throw new DictionaryException("Stopwords file was updated: old=" + oldItem + " : new=" + item);
+                    }
+                } else {
+                    writer.write(oldItem.toLineString());
+                    writer.write(Constants.LINE_SEPARATOR);
+                    return oldItem;
+                }
+            } catch (final IOException e) {
+                throw new DictionaryException("Failed to write: " + oldItem + " -> " + item, e);
+            }
+        }
+
+        public void write(final String line) {
+            try {
+                writer.write(line);
+                writer.write(Constants.LINE_SEPARATOR);
+            } catch (final IOException e) {
+                throw new DictionaryException("Failed to write: " + line, e);
+            }
+        }
+
+        public StopwordsItem commit() {
+            isCommit = true;
+            if (item != null && item.isUpdated()) {
+                try {
+                    writer.write(item.toLineString());
+                    writer.write(Constants.LINE_SEPARATOR);
+                    return item;
+                } catch (final IOException e) {
+                    throw new DictionaryException("Failed to write: " + item, e);
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public void close() {
+            try {
+                writer.flush();
+            } catch (final IOException e) {
+                // ignore
+            }
+            CloseableUtil.closeQuietly(writer);
+
+            if (isCommit) {
+                try {
+                    dictionaryManager.store(StopwordsFile.this, newFile);
+                } finally {
+                    newFile.delete();
+                }
+            } else {
+                newFile.delete();
+            }
+        }
+    }
+}

+ 103 - 0
src/main/java/org/codelibs/fess/dict/stopwords/StopwordsItem.java

@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.dict.stopwords;
+
+import org.apache.commons.lang3.StringUtils;
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.fess.dict.DictionaryItem;
+
+public class StopwordsItem extends DictionaryItem {
+    private final String input;
+
+    private String newInput;
+
+    public StopwordsItem(final long id, final String input) {
+        this.id = id;
+        this.input = input;
+
+        if (id == 0) {
+            // create
+            newInput = input;
+        }
+    }
+
+    public String getNewInput() {
+        return newInput;
+    }
+
+    public void setNewInput(final String newInput) {
+        this.newInput = newInput;
+    }
+
+    public String getInput() {
+        return input;
+    }
+
+    public String getInputValue() {
+        if (input == null) {
+            return StringUtil.EMPTY;
+        }
+        return input;
+    }
+
+    public boolean isUpdated() {
+        return newInput != null;
+    }
+
+    public boolean isDeleted() {
+        return isUpdated() && newInput.length() == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + input.hashCode();
+        return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final StopwordsItem other = (StopwordsItem) obj;
+        if (!input.equals(other.input)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "StopwordsItem [id=" + id + ", inputs=" + input + ", newInputs=" + newInput + "]";
+    }
+
+    public String toLineString() {
+        if (isUpdated()) {
+            return StringUtils.join(newInput);
+        } else {
+            return StringUtils.join(input);
+        }
+    }
+
+}

+ 17 - 4
src/main/java/org/codelibs/fess/mylasta/action/FessHtmlPath.java

@@ -123,8 +123,7 @@ public interface FessHtmlPath {
     HtmlNext path_AdminDictProtwords_AdminDictProtwordsDetailsJsp = new HtmlNext("/admin/dict/protwords/admin_dict_protwords_details.jsp");
 
     /** The path of the HTML: /admin/dict/protwords/admin_dict_protwords_download.jsp */
-    HtmlNext path_AdminDictProtwords_AdminDictProtwordsDownloadJsp =
-            new HtmlNext("/admin/dict/protwords/admin_dict_protwords_download.jsp");
+    HtmlNext path_AdminDictProtwords_AdminDictProtwordsDownloadJsp = new HtmlNext("/admin/dict/protwords/admin_dict_protwords_download.jsp");
 
     /** The path of the HTML: /admin/dict/protwords/admin_dict_protwords_edit.jsp */
     HtmlNext path_AdminDictProtwords_AdminDictProtwordsEditJsp = new HtmlNext("/admin/dict/protwords/admin_dict_protwords_edit.jsp");
@@ -132,6 +131,21 @@ public interface FessHtmlPath {
     /** The path of the HTML: /admin/dict/protwords/admin_dict_protwords_upload.jsp */
     HtmlNext path_AdminDictProtwords_AdminDictProtwordsUploadJsp = new HtmlNext("/admin/dict/protwords/admin_dict_protwords_upload.jsp");
 
+    /** The path of the HTML: /admin/dict/stopwords/admin_dict_stopwords.jsp */
+    HtmlNext path_AdminDictStopwords_AdminDictStopwordsJsp = new HtmlNext("/admin/dict/stopwords/admin_dict_stopwords.jsp");
+
+    /** The path of the HTML: /admin/dict/stopwords/admin_dict_stopwords_details.jsp */
+    HtmlNext path_AdminDictStopwords_AdminDictStopwordsDetailsJsp = new HtmlNext("/admin/dict/stopwords/admin_dict_stopwords_details.jsp");
+
+    /** The path of the HTML: /admin/dict/stopwords/admin_dict_stopwords_download.jsp */
+    HtmlNext path_AdminDictStopwords_AdminDictStopwordsDownloadJsp = new HtmlNext("/admin/dict/stopwords/admin_dict_stopwords_download.jsp");
+
+    /** The path of the HTML: /admin/dict/stopwords/admin_dict_stopwords_edit.jsp */
+    HtmlNext path_AdminDictStopwords_AdminDictStopwordsEditJsp = new HtmlNext("/admin/dict/stopwords/admin_dict_stopwords_edit.jsp");
+
+    /** The path of the HTML: /admin/dict/stopwords/admin_dict_stopwords_upload.jsp */
+    HtmlNext path_AdminDictStopwords_AdminDictStopwordsUploadJsp = new HtmlNext("/admin/dict/stopwords/admin_dict_stopwords_upload.jsp");
+
     /** The path of the HTML: /admin/dict/synonym/admin_dict_synonym.jsp */
     HtmlNext path_AdminDictSynonym_AdminDictSynonymJsp = new HtmlNext("/admin/dict/synonym/admin_dict_synonym.jsp");
 
@@ -256,8 +270,7 @@ public interface FessHtmlPath {
     HtmlNext path_AdminRelatedcontent_AdminRelatedcontentJsp = new HtmlNext("/admin/relatedcontent/admin_relatedcontent.jsp");
 
     /** The path of the HTML: /admin/relatedcontent/admin_relatedcontent_details.jsp */
-    HtmlNext path_AdminRelatedcontent_AdminRelatedcontentDetailsJsp =
-            new HtmlNext("/admin/relatedcontent/admin_relatedcontent_details.jsp");
+    HtmlNext path_AdminRelatedcontent_AdminRelatedcontentDetailsJsp = new HtmlNext("/admin/relatedcontent/admin_relatedcontent_details.jsp");
 
     /** The path of the HTML: /admin/relatedcontent/admin_relatedcontent_edit.jsp */
     HtmlNext path_AdminRelatedcontent_AdminRelatedcontentEditJsp = new HtmlNext("/admin/relatedcontent/admin_relatedcontent_edit.jsp");

+ 45 - 7
src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java

@@ -623,10 +623,10 @@ public class FessLabels extends UserMessages {
     /** The key of the message: Fess */
     public static final String LABELS_search_title = "{labels.search_title}";
 
-    /** The key of the message: Popular Words:  */
+    /** The key of the message: Popular Words: */
     public static final String LABELS_search_popular_word_word = "{labels.search_popular_word_word}";
 
-    /** The key of the message: Related Words:  */
+    /** The key of the message: Related Words: */
     public static final String LABELS_search_related_queries = "{labels.search_related_queries}";
 
     /** The key of the message: -- Sort -- */
@@ -680,10 +680,10 @@ public class FessLabels extends UserMessages {
     /** The key of the message: {0} bytes */
     public static final String LABELS_search_result_size = "{labels.search_result_size}";
 
-    /** The key of the message: Registered:  */
+    /** The key of the message: Registered: */
     public static final String LABELS_search_result_created = "{labels.search_result_created}";
 
-    /** The key of the message: Last Modified:  */
+    /** The key of the message: Last Modified: */
     public static final String LABELS_search_result_last_modified = "{labels.search_result_last_modified}";
 
     /** The key of the message: Like */
@@ -1686,8 +1686,7 @@ public class FessLabels extends UserMessages {
     public static final String LABELS_system_info_bug_report_title = "{labels.system_info_bug_report_title}";
 
     /** The key of the message: system.properties does not exist. Default values are applied. */
-    public static final String LABELS_system_info_system_properties_does_not_exist =
-            "{labels.system_info_system_properties_does_not_exist}";
+    public static final String LABELS_system_info_system_properties_does_not_exist = "{labels.system_info_system_properties_does_not_exist}";
 
     /** The key of the message: File Authentication */
     public static final String LABELS_file_auth_configuration = "{labels.file_auth_configuration}";
@@ -2031,6 +2030,45 @@ public class FessLabels extends UserMessages {
     /** The key of the message: Protwords File */
     public static final String LABELS_dict_protwords_file = "{labels.dict_protwords_file}";
 
+    /** The key of the message: Stopwords List */
+    public static final String LABELS_dict_stopwords_configuration = "{labels.dict_stopwords_configuration}";
+
+    /** The key of the message: Stopwords List */
+    public static final String LABELS_dict_stopwords_title = "{labels.dict_stopwords_title}";
+
+    /** The key of the message: List */
+    public static final String LABELS_dict_stopwords_list_link = "{labels.dict_stopwords_list_link}";
+
+    /** The key of the message: Create New */
+    public static final String LABELS_dict_stopwords_link_create = "{labels.dict_stopwords_link_create}";
+
+    /** The key of the message: Edit */
+    public static final String LABELS_dict_stopwords_link_edit = "{labels.dict_stopwords_link_edit}";
+
+    /** The key of the message: Delete */
+    public static final String LABELS_dict_stopwords_link_delete = "{labels.dict_stopwords_link_delete}";
+
+    /** The key of the message: Details */
+    public static final String LABELS_dict_stopwords_link_details = "{labels.dict_stopwords_link_details}";
+
+    /** The key of the message: Download */
+    public static final String LABELS_dict_stopwords_link_download = "{labels.dict_stopwords_link_download}";
+
+    /** The key of the message: Upload */
+    public static final String LABELS_dict_stopwords_link_upload = "{labels.dict_stopwords_link_upload}";
+
+    /** The key of the message: Source */
+    public static final String LABELS_dict_stopwords_source = "{labels.dict_stopwords_source}";
+
+    /** The key of the message: Download */
+    public static final String LABELS_dict_stopwords_button_download = "{labels.dict_stopwords_button_download}";
+
+    /** The key of the message: Upload */
+    public static final String LABELS_dict_stopwords_button_upload = "{labels.dict_stopwords_button_upload}";
+
+    /** The key of the message: Stopwords File */
+    public static final String LABELS_dict_stopwords_file = "{labels.dict_stopwords_file}";
+
     /** The key of the message: Doc Boost */
     public static final String LABELS_boost_document_rule_configuration = "{labels.boost_document_rule_configuration}";
 
@@ -2646,7 +2684,7 @@ public class FessLabels extends UserMessages {
     /** The key of the message: No match */
     public static final String LABELS_facet_is_not_found = "{labels.facet_is_not_found}";
 
-    /** The key of the message: Score:  */
+    /** The key of the message: Score: */
     public static final String LABELS_doc_score = "{labels.doc_score}";
 
     /** The key of the message: Running as Development mode. For production use, please install a standalone elasticsearch server. */

+ 35 - 0
src/main/java/org/codelibs/fess/mylasta/action/FessMessages.java

@@ -15,6 +15,7 @@
  */
 package org.codelibs.fess.mylasta.action;
 
+import org.codelibs.fess.mylasta.action.FessLabels;
 import org.lastaflute.core.message.UserMessage;
 
 /**
@@ -263,6 +264,12 @@ public class FessMessages extends FessLabels {
     /** The key of the message: Failed to upload the Protwords file. */
     public static final String ERRORS_failed_to_upload_protwords_file = "{errors.failed_to_upload_protwords_file}";
 
+    /** The key of the message: Failed to download the Stopwords file. */
+    public static final String ERRORS_failed_to_download_stopwords_file = "{errors.failed_to_download_stopwords_file}";
+
+    /** The key of the message: Failed to upload the Stopwords file. */
+    public static final String ERRORS_failed_to_upload_stopwords_file = "{errors.failed_to_upload_stopwords_file}";
+
     /** The key of the message: Failed to download the Elevate file. */
     public static final String ERRORS_failed_to_download_elevate_file = "{errors.failed_to_download_elevate_file}";
 
@@ -1603,6 +1610,34 @@ public class FessMessages extends FessLabels {
         return this;
     }
 
+    /**
+     * Add the created action message for the key 'errors.failed_to_download_stopwords_file' with parameters.
+     * <pre>
+     * message: Failed to download the Stopwords file.
+     * </pre>
+     * @param property The property name for the message. (NotNull)
+     * @return this. (NotNull)
+     */
+    public FessMessages addErrorsFailedToDownloadStopwordsFile(String property) {
+        assertPropertyNotNull(property);
+        add(property, new UserMessage(ERRORS_failed_to_download_stopwords_file));
+        return this;
+    }
+
+    /**
+     * Add the created action message for the key 'errors.failed_to_upload_stopwords_file' with parameters.
+     * <pre>
+     * message: Failed to upload the Stopwords file.
+     * </pre>
+     * @param property The property name for the message. (NotNull)
+     * @return this. (NotNull)
+     */
+    public FessMessages addErrorsFailedToUploadStopwordsFile(String property) {
+        assertPropertyNotNull(property);
+        add(property, new UserMessage(ERRORS_failed_to_upload_stopwords_file));
+        return this;
+    }
+
     /**
      * Add the created action message for the key 'errors.failed_to_download_elevate_file' with parameters.
      * <pre>

+ 306 - 321
src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java

@@ -1,20 +1,6 @@
-/*
- * Copyright 2012-2018 CodeLibs Project and the Others.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- * either express or implied. See the License for the specific language
- * governing permissions and limitations under the License.
- */
 package org.codelibs.fess.mylasta.direction;
 
+import org.codelibs.fess.mylasta.direction.FessEnv;
 import org.lastaflute.core.direction.exception.ConfigPropertyNotFoundException;
 
 /**
@@ -50,92 +36,92 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     String APP_DIGEST_ALGORISM = "app.digest.algorism";
 
     /** The key of the configuration. e.g. -Djava.awt.headless=true
-    -Dfile.encoding=UTF-8
-    -Djna.nosys=true
-    -Djdk.io.permissionsUseCanonicalPath=true
-    -server
-    -Xmx512m
-    -XX:MaxMetaspaceSize=128m
-    -XX:CompressedClassSpaceSize=32m
-    -XX:-UseGCOverheadLimit
-    -XX:+UseConcMarkSweepGC
-    -XX:CMSInitiatingOccupancyFraction=75
-    -XX:+UseCMSInitiatingOccupancyOnly
-    -XX:+UseTLAB
-    -XX:+DisableExplicitGC
-    -XX:+HeapDumpOnOutOfMemoryError
-    -XX:-OmitStackTraceInFastThrow
-    -Djcifs.smb.client.responseTimeout=30000
-    -Djcifs.smb.client.soTimeout=35000
-    -Djcifs.smb.client.connTimeout=60000
-    -Djcifs.smb.client.sessionTimeout=60000
-    -Dgroovy.use.classvalue=true
-    -Dio.netty.noUnsafe=true
-    -Dio.netty.noKeySetOptimization=true
-    -Dio.netty.recycler.maxCapacityPerThread=0
-    -Dlog4j.shutdownHookEnabled=false
-    -Dlog4j2.disable.jmx=true
-    -Dlog4j.skipJansi=true
-    -Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider
-    -Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true
-    */
+-Dfile.encoding=UTF-8
+-Djna.nosys=true
+-Djdk.io.permissionsUseCanonicalPath=true
+-server
+-Xmx512m
+-XX:MaxMetaspaceSize=128m
+-XX:CompressedClassSpaceSize=32m
+-XX:-UseGCOverheadLimit
+-XX:+UseConcMarkSweepGC
+-XX:CMSInitiatingOccupancyFraction=75
+-XX:+UseCMSInitiatingOccupancyOnly
+-XX:+UseTLAB
+-XX:+DisableExplicitGC
+-XX:+HeapDumpOnOutOfMemoryError
+-XX:-OmitStackTraceInFastThrow
+-Djcifs.smb.client.responseTimeout=30000
+-Djcifs.smb.client.soTimeout=35000
+-Djcifs.smb.client.connTimeout=60000
+-Djcifs.smb.client.sessionTimeout=60000
+-Dgroovy.use.classvalue=true
+-Dio.netty.noUnsafe=true
+-Dio.netty.noKeySetOptimization=true
+-Dio.netty.recycler.maxCapacityPerThread=0
+-Dlog4j.shutdownHookEnabled=false
+-Dlog4j2.disable.jmx=true
+-Dlog4j.skipJansi=true
+-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider
+-Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true
+ */
     String JVM_CRAWLER_OPTIONS = "jvm.crawler.options";
 
     /** The key of the configuration. e.g. -Djava.awt.headless=true
-    -Dfile.encoding=UTF-8
-    -Djna.nosys=true
-    -Djdk.io.permissionsUseCanonicalPath=true
-    -server
-    -Xmx256m
-    -XX:MaxMetaspaceSize=128m
-    -XX:CompressedClassSpaceSize=32m
-    -XX:-UseGCOverheadLimit
-    -XX:+UseConcMarkSweepGC
-    -XX:CMSInitiatingOccupancyFraction=75
-    -XX:+UseCMSInitiatingOccupancyOnly
-    -XX:+UseTLAB
-    -XX:+DisableExplicitGC
-    -XX:+HeapDumpOnOutOfMemoryError
-    -Dgroovy.use.classvalue=true
-    -Dio.netty.noUnsafe=true
-    -Dio.netty.noKeySetOptimization=true
-    -Dio.netty.recycler.maxCapacityPerThread=0
-    -Dlog4j.shutdownHookEnabled=false
-    -Dlog4j2.disable.jmx=true
-    -Dlog4j.skipJansi=true
-    */
+-Dfile.encoding=UTF-8
+-Djna.nosys=true
+-Djdk.io.permissionsUseCanonicalPath=true
+-server
+-Xmx256m
+-XX:MaxMetaspaceSize=128m
+-XX:CompressedClassSpaceSize=32m
+-XX:-UseGCOverheadLimit
+-XX:+UseConcMarkSweepGC
+-XX:CMSInitiatingOccupancyFraction=75
+-XX:+UseCMSInitiatingOccupancyOnly
+-XX:+UseTLAB
+-XX:+DisableExplicitGC
+-XX:+HeapDumpOnOutOfMemoryError
+-Dgroovy.use.classvalue=true
+-Dio.netty.noUnsafe=true
+-Dio.netty.noKeySetOptimization=true
+-Dio.netty.recycler.maxCapacityPerThread=0
+-Dlog4j.shutdownHookEnabled=false
+-Dlog4j2.disable.jmx=true
+-Dlog4j.skipJansi=true
+ */
     String JVM_SUGGEST_OPTIONS = "jvm.suggest.options";
 
     /** The key of the configuration. e.g. -Djava.awt.headless=true
-    -Dfile.encoding=UTF-8
-    -Djna.nosys=true
-    -Djdk.io.permissionsUseCanonicalPath=true
-    -server
-    -Xmx128m
-    -XX:MaxMetaspaceSize=128m
-    -XX:CompressedClassSpaceSize=32m
-    -XX:-UseGCOverheadLimit
-    -XX:+UseConcMarkSweepGC
-    -XX:CMSInitiatingOccupancyFraction=75
-    -XX:+UseCMSInitiatingOccupancyOnly
-    -XX:+UseTLAB
-    -XX:+DisableExplicitGC
-    -XX:+HeapDumpOnOutOfMemoryError
-    -XX:-OmitStackTraceInFastThrow
-    -Djcifs.smb.client.responseTimeout=30000
-    -Djcifs.smb.client.soTimeout=35000
-    -Djcifs.smb.client.connTimeout=60000
-    -Djcifs.smb.client.sessionTimeout=60000
-    -Dgroovy.use.classvalue=true
-    -Dio.netty.noUnsafe=true
-    -Dio.netty.noKeySetOptimization=true
-    -Dio.netty.recycler.maxCapacityPerThread=0
-    -Dlog4j.shutdownHookEnabled=false
-    -Dlog4j2.disable.jmx=true
-    -Dlog4j.skipJansi=true
-    -Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider
-    -Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true
-    */
+-Dfile.encoding=UTF-8
+-Djna.nosys=true
+-Djdk.io.permissionsUseCanonicalPath=true
+-server
+-Xmx128m
+-XX:MaxMetaspaceSize=128m
+-XX:CompressedClassSpaceSize=32m
+-XX:-UseGCOverheadLimit
+-XX:+UseConcMarkSweepGC
+-XX:CMSInitiatingOccupancyFraction=75
+-XX:+UseCMSInitiatingOccupancyOnly
+-XX:+UseTLAB
+-XX:+DisableExplicitGC
+-XX:+HeapDumpOnOutOfMemoryError
+-XX:-OmitStackTraceInFastThrow
+-Djcifs.smb.client.responseTimeout=30000
+-Djcifs.smb.client.soTimeout=35000
+-Djcifs.smb.client.connTimeout=60000
+-Djcifs.smb.client.sessionTimeout=60000
+-Dgroovy.use.classvalue=true
+-Dio.netty.noUnsafe=true
+-Dio.netty.noKeySetOptimization=true
+-Dio.netty.recycler.maxCapacityPerThread=0
+-Dlog4j.shutdownHookEnabled=false
+-Dlog4j2.disable.jmx=true
+-Dlog4j.skipJansi=true
+-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider
+-Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true
+ */
     String JVM_THUMBNAIL_OPTIONS = "jvm.thumbnail.options";
 
     /** The key of the configuration. e.g. default_crawler */
@@ -271,8 +257,8 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     String CRAWLER_METADATA_CONTENT_EXCLUDES = "crawler.metadata.content.excludes";
 
     /** The key of the configuration. e.g. title=title:string
-    Title=title:string
-    */
+Title=title:string
+ */
     String CRAWLER_METADATA_NAME_MAPPING = "crawler.metadata.name.mapping";
 
     /** The key of the configuration. e.g. //BODY */
@@ -684,65 +670,65 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     String QUERY_GSA_DEFAULT_PREFERENCE = "query.gsa.default.preference";
 
     /** The key of the configuration. e.g. ar=ar
-    bg=bg
-    bn=bn
-    ca=ca
-    ckb-iq=ckb-iq
-    ckb_IQ=ckb-iq
-    cs=cs
-    da=da
-    de=de
-    el=el
-    en=en
-    en-ie=en-ie
-    en_IE=en-ie
-    es=es
-    et=et
-    eu=eu
-    fa=fa
-    fi=fi
-    fr=fr
-    gl=gl
-    gu=gu
-    he=he
-    hi=hi
-    hr=hr
-    hu=hu
-    hy=hy
-    id=id
-    it=it
-    ja=ja
-    ko=ko
-    lt=lt
-    lv=lv
-    mk=mk
-    ml=ml
-    nl=nl
-    no=no
-    pa=pa
-    pl=pl
-    pt=pt
-    pt-br=pt-br
-    pt_BR=pt-br
-    ro=ro
-    ru=ru
-    si=si
-    sq=sq
-    sv=sv
-    ta=ta
-    te=te
-    th=th
-    tl=tl
-    tr=tr
-    uk=uk
-    ur=ur
-    vi=vi
-    zh-cn=zh-cn
-    zh_CN=zh-cn
-    zh-tw=zh-tw
-    zh_TW=zh-tw
-    zh=zh
-    */
+bg=bg
+bn=bn
+ca=ca
+ckb-iq=ckb-iq
+ckb_IQ=ckb-iq
+cs=cs
+da=da
+de=de
+el=el
+en=en
+en-ie=en-ie
+en_IE=en-ie
+es=es
+et=et
+eu=eu
+fa=fa
+fi=fi
+fr=fr
+gl=gl
+gu=gu
+he=he
+hi=hi
+hr=hr
+hu=hu
+hy=hy
+id=id
+it=it
+ja=ja
+ko=ko
+lt=lt
+lv=lv
+mk=mk
+ml=ml
+nl=nl
+no=no
+pa=pa
+pl=pl
+pt=pt
+pt-br=pt-br
+pt_BR=pt-br
+ro=ro
+ru=ru
+si=si
+sq=sq
+sv=sv
+ta=ta
+te=te
+th=th
+tl=tl
+tr=tr
+uk=uk
+ur=ur
+vi=vi
+zh-cn=zh-cn
+zh_CN=zh-cn
+zh-tw=zh-tw
+zh_TW=zh-tw
+zh=zh
+ */
     String QUERY_LANGUAGE_MAPPING = "query.language.mapping";
 
     /** The key of the configuration. e.g. 0.2 */
@@ -1045,6 +1031,9 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /** The key of the configuration. e.g. protwords */
     String ONLINE_HELP_NAME_DICT_PROTWORDS = "online.help.name.dict.protwords";
 
+    /** The key of the configuration. e.g. stopwords */
+    String ONLINE_HELP_NAME_DICT_STOPWORDS = "online.help.name.dict.stopwords";
+
     /** The key of the configuration. e.g. mapping */
     String ONLINE_HELP_NAME_DICT_MAPPING = "online.help.name.dict.mapping";
 
@@ -1524,35 +1513,35 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /**
      * Get the value for the key 'jvm.crawler.options'. <br>
      * The value is, e.g. -Djava.awt.headless=true
-    -Dfile.encoding=UTF-8
-    -Djna.nosys=true
-    -Djdk.io.permissionsUseCanonicalPath=true
-    -server
-    -Xmx512m
-    -XX:MaxMetaspaceSize=128m
-    -XX:CompressedClassSpaceSize=32m
-    -XX:-UseGCOverheadLimit
-    -XX:+UseConcMarkSweepGC
-    -XX:CMSInitiatingOccupancyFraction=75
-    -XX:+UseCMSInitiatingOccupancyOnly
-    -XX:+UseTLAB
-    -XX:+DisableExplicitGC
-    -XX:+HeapDumpOnOutOfMemoryError
-    -XX:-OmitStackTraceInFastThrow
-    -Djcifs.smb.client.responseTimeout=30000
-    -Djcifs.smb.client.soTimeout=35000
-    -Djcifs.smb.client.connTimeout=60000
-    -Djcifs.smb.client.sessionTimeout=60000
-    -Dgroovy.use.classvalue=true
-    -Dio.netty.noUnsafe=true
-    -Dio.netty.noKeySetOptimization=true
-    -Dio.netty.recycler.maxCapacityPerThread=0
-    -Dlog4j.shutdownHookEnabled=false
-    -Dlog4j2.disable.jmx=true
-    -Dlog4j.skipJansi=true
-    -Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider
-    -Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true
-    <br>
+-Dfile.encoding=UTF-8
+-Djna.nosys=true
+-Djdk.io.permissionsUseCanonicalPath=true
+-server
+-Xmx512m
+-XX:MaxMetaspaceSize=128m
+-XX:CompressedClassSpaceSize=32m
+-XX:-UseGCOverheadLimit
+-XX:+UseConcMarkSweepGC
+-XX:CMSInitiatingOccupancyFraction=75
+-XX:+UseCMSInitiatingOccupancyOnly
+-XX:+UseTLAB
+-XX:+DisableExplicitGC
+-XX:+HeapDumpOnOutOfMemoryError
+-XX:-OmitStackTraceInFastThrow
+-Djcifs.smb.client.responseTimeout=30000
+-Djcifs.smb.client.soTimeout=35000
+-Djcifs.smb.client.connTimeout=60000
+-Djcifs.smb.client.sessionTimeout=60000
+-Dgroovy.use.classvalue=true
+-Dio.netty.noUnsafe=true
+-Dio.netty.noKeySetOptimization=true
+-Dio.netty.recycler.maxCapacityPerThread=0
+-Dlog4j.shutdownHookEnabled=false
+-Dlog4j2.disable.jmx=true
+-Dlog4j.skipJansi=true
+-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider
+-Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true
+ <br>
      * comment: JVM options
      * @return The value of found property. (NotNull: if not found, exception but basically no way)
      */
@@ -1561,28 +1550,28 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /**
      * Get the value for the key 'jvm.suggest.options'. <br>
      * The value is, e.g. -Djava.awt.headless=true
-    -Dfile.encoding=UTF-8
-    -Djna.nosys=true
-    -Djdk.io.permissionsUseCanonicalPath=true
-    -server
-    -Xmx256m
-    -XX:MaxMetaspaceSize=128m
-    -XX:CompressedClassSpaceSize=32m
-    -XX:-UseGCOverheadLimit
-    -XX:+UseConcMarkSweepGC
-    -XX:CMSInitiatingOccupancyFraction=75
-    -XX:+UseCMSInitiatingOccupancyOnly
-    -XX:+UseTLAB
-    -XX:+DisableExplicitGC
-    -XX:+HeapDumpOnOutOfMemoryError
-    -Dgroovy.use.classvalue=true
-    -Dio.netty.noUnsafe=true
-    -Dio.netty.noKeySetOptimization=true
-    -Dio.netty.recycler.maxCapacityPerThread=0
-    -Dlog4j.shutdownHookEnabled=false
-    -Dlog4j2.disable.jmx=true
-    -Dlog4j.skipJansi=true
-    <br>
+-Dfile.encoding=UTF-8
+-Djna.nosys=true
+-Djdk.io.permissionsUseCanonicalPath=true
+-server
+-Xmx256m
+-XX:MaxMetaspaceSize=128m
+-XX:CompressedClassSpaceSize=32m
+-XX:-UseGCOverheadLimit
+-XX:+UseConcMarkSweepGC
+-XX:CMSInitiatingOccupancyFraction=75
+-XX:+UseCMSInitiatingOccupancyOnly
+-XX:+UseTLAB
+-XX:+DisableExplicitGC
+-XX:+HeapDumpOnOutOfMemoryError
+-Dgroovy.use.classvalue=true
+-Dio.netty.noUnsafe=true
+-Dio.netty.noKeySetOptimization=true
+-Dio.netty.recycler.maxCapacityPerThread=0
+-Dlog4j.shutdownHookEnabled=false
+-Dlog4j2.disable.jmx=true
+-Dlog4j.skipJansi=true
+ <br>
      * @return The value of found property. (NotNull: if not found, exception but basically no way)
      */
     String getJvmSuggestOptions();
@@ -1590,35 +1579,35 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /**
      * Get the value for the key 'jvm.thumbnail.options'. <br>
      * The value is, e.g. -Djava.awt.headless=true
-    -Dfile.encoding=UTF-8
-    -Djna.nosys=true
-    -Djdk.io.permissionsUseCanonicalPath=true
-    -server
-    -Xmx128m
-    -XX:MaxMetaspaceSize=128m
-    -XX:CompressedClassSpaceSize=32m
-    -XX:-UseGCOverheadLimit
-    -XX:+UseConcMarkSweepGC
-    -XX:CMSInitiatingOccupancyFraction=75
-    -XX:+UseCMSInitiatingOccupancyOnly
-    -XX:+UseTLAB
-    -XX:+DisableExplicitGC
-    -XX:+HeapDumpOnOutOfMemoryError
-    -XX:-OmitStackTraceInFastThrow
-    -Djcifs.smb.client.responseTimeout=30000
-    -Djcifs.smb.client.soTimeout=35000
-    -Djcifs.smb.client.connTimeout=60000
-    -Djcifs.smb.client.sessionTimeout=60000
-    -Dgroovy.use.classvalue=true
-    -Dio.netty.noUnsafe=true
-    -Dio.netty.noKeySetOptimization=true
-    -Dio.netty.recycler.maxCapacityPerThread=0
-    -Dlog4j.shutdownHookEnabled=false
-    -Dlog4j2.disable.jmx=true
-    -Dlog4j.skipJansi=true
-    -Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider
-    -Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true
-    <br>
+-Dfile.encoding=UTF-8
+-Djna.nosys=true
+-Djdk.io.permissionsUseCanonicalPath=true
+-server
+-Xmx128m
+-XX:MaxMetaspaceSize=128m
+-XX:CompressedClassSpaceSize=32m
+-XX:-UseGCOverheadLimit
+-XX:+UseConcMarkSweepGC
+-XX:CMSInitiatingOccupancyFraction=75
+-XX:+UseCMSInitiatingOccupancyOnly
+-XX:+UseTLAB
+-XX:+DisableExplicitGC
+-XX:+HeapDumpOnOutOfMemoryError
+-XX:-OmitStackTraceInFastThrow
+-Djcifs.smb.client.responseTimeout=30000
+-Djcifs.smb.client.soTimeout=35000
+-Djcifs.smb.client.connTimeout=60000
+-Djcifs.smb.client.sessionTimeout=60000
+-Dgroovy.use.classvalue=true
+-Dio.netty.noUnsafe=true
+-Dio.netty.noKeySetOptimization=true
+-Dio.netty.recycler.maxCapacityPerThread=0
+-Dlog4j.shutdownHookEnabled=false
+-Dlog4j2.disable.jmx=true
+-Dlog4j.skipJansi=true
+-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider
+-Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true
+ <br>
      * @return The value of found property. (NotNull: if not found, exception but basically no way)
      */
     String getJvmThumbnailOptions();
@@ -2112,8 +2101,8 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /**
      * Get the value for the key 'crawler.metadata.name.mapping'. <br>
      * The value is, e.g. title=title:string
-    Title=title:string
-    <br>
+Title=title:string
+ <br>
      * @return The value of found property. (NotNull: if not found, exception but basically no way)
      */
     String getCrawlerMetadataNameMapping();
@@ -3534,65 +3523,65 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /**
      * Get the value for the key 'query.language.mapping'. <br>
      * The value is, e.g. ar=ar
-    bg=bg
-    bn=bn
-    ca=ca
-    ckb-iq=ckb-iq
-    ckb_IQ=ckb-iq
-    cs=cs
-    da=da
-    de=de
-    el=el
-    en=en
-    en-ie=en-ie
-    en_IE=en-ie
-    es=es
-    et=et
-    eu=eu
-    fa=fa
-    fi=fi
-    fr=fr
-    gl=gl
-    gu=gu
-    he=he
-    hi=hi
-    hr=hr
-    hu=hu
-    hy=hy
-    id=id
-    it=it
-    ja=ja
-    ko=ko
-    lt=lt
-    lv=lv
-    mk=mk
-    ml=ml
-    nl=nl
-    no=no
-    pa=pa
-    pl=pl
-    pt=pt
-    pt-br=pt-br
-    pt_BR=pt-br
-    ro=ro
-    ru=ru
-    si=si
-    sq=sq
-    sv=sv
-    ta=ta
-    te=te
-    th=th
-    tl=tl
-    tr=tr
-    uk=uk
-    ur=ur
-    vi=vi
-    zh-cn=zh-cn
-    zh_CN=zh-cn
-    zh-tw=zh-tw
-    zh_TW=zh-tw
-    zh=zh
-    <br>
+bg=bg
+bn=bn
+ca=ca
+ckb-iq=ckb-iq
+ckb_IQ=ckb-iq
+cs=cs
+da=da
+de=de
+el=el
+en=en
+en-ie=en-ie
+en_IE=en-ie
+es=es
+et=et
+eu=eu
+fa=fa
+fi=fi
+fr=fr
+gl=gl
+gu=gu
+he=he
+hi=hi
+hr=hr
+hu=hu
+hy=hy
+id=id
+it=it
+ja=ja
+ko=ko
+lt=lt
+lv=lv
+mk=mk
+ml=ml
+nl=nl
+no=no
+pa=pa
+pl=pl
+pt=pt
+pt-br=pt-br
+pt_BR=pt-br
+ro=ro
+ru=ru
+si=si
+sq=sq
+sv=sv
+ta=ta
+te=te
+th=th
+tl=tl
+tr=tr
+uk=uk
+ur=ur
+vi=vi
+zh-cn=zh-cn
+zh_CN=zh-cn
+zh-tw=zh-tw
+zh_TW=zh-tw
+zh=zh
+ <br>
      * @return The value of found property. (NotNull: if not found, exception but basically no way)
      */
     String getQueryLanguageMapping();
@@ -4879,6 +4868,13 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
      */
     String getOnlineHelpNameDictProtwords();
 
+    /**
+     * Get the value for the key 'online.help.name.dict.stopwords'. <br>
+     * The value is, e.g. stopwords <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getOnlineHelpNameDictStopwords();
+
     /**
      * Get the value for the key 'online.help.name.dict.mapping'. <br>
      * The value is, e.g. mapping <br>
@@ -7797,6 +7793,10 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             return get(FessConfig.ONLINE_HELP_NAME_DICT_PROTWORDS);
         }
 
+        public String getOnlineHelpNameDictStopwords() {
+            return get(FessConfig.ONLINE_HELP_NAME_DICT_STOPWORDS);
+        }
+
         public String getOnlineHelpNameDictMapping() {
             return get(FessConfig.ONLINE_HELP_NAME_DICT_MAPPING);
         }
@@ -8441,22 +8441,14 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             defaultMap.put(FessConfig.APP_CIPHER_ALGORISM, "aes");
             defaultMap.put(FessConfig.APP_CIPHER_KEY, "___change__me___");
             defaultMap.put(FessConfig.APP_DIGEST_ALGORISM, "sha256");
-            defaultMap
-                    .put(FessConfig.JVM_CRAWLER_OPTIONS,
-                            "-Djava.awt.headless=true\n-Dfile.encoding=UTF-8\n-Djna.nosys=true\n-Djdk.io.permissionsUseCanonicalPath=true\n-server\n-Xmx512m\n-XX:MaxMetaspaceSize=128m\n-XX:CompressedClassSpaceSize=32m\n-XX:-UseGCOverheadLimit\n-XX:+UseConcMarkSweepGC\n-XX:CMSInitiatingOccupancyFraction=75\n-XX:+UseCMSInitiatingOccupancyOnly\n-XX:+UseTLAB\n-XX:+DisableExplicitGC\n-XX:+HeapDumpOnOutOfMemoryError\n-XX:-OmitStackTraceInFastThrow\n-Djcifs.smb.client.responseTimeout=30000\n-Djcifs.smb.client.soTimeout=35000\n-Djcifs.smb.client.connTimeout=60000\n-Djcifs.smb.client.sessionTimeout=60000\n-Dgroovy.use.classvalue=true\n-Dio.netty.noUnsafe=true\n-Dio.netty.noKeySetOptimization=true\n-Dio.netty.recycler.maxCapacityPerThread=0\n-Dlog4j.shutdownHookEnabled=false\n-Dlog4j2.disable.jmx=true\n-Dlog4j.skipJansi=true\n-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider\n-Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true\n");
-            defaultMap
-                    .put(FessConfig.JVM_SUGGEST_OPTIONS,
-                            "-Djava.awt.headless=true\n-Dfile.encoding=UTF-8\n-Djna.nosys=true\n-Djdk.io.permissionsUseCanonicalPath=true\n-server\n-Xmx256m\n-XX:MaxMetaspaceSize=128m\n-XX:CompressedClassSpaceSize=32m\n-XX:-UseGCOverheadLimit\n-XX:+UseConcMarkSweepGC\n-XX:CMSInitiatingOccupancyFraction=75\n-XX:+UseCMSInitiatingOccupancyOnly\n-XX:+UseTLAB\n-XX:+DisableExplicitGC\n-XX:+HeapDumpOnOutOfMemoryError\n-Dgroovy.use.classvalue=true\n-Dio.netty.noUnsafe=true\n-Dio.netty.noKeySetOptimization=true\n-Dio.netty.recycler.maxCapacityPerThread=0\n-Dlog4j.shutdownHookEnabled=false\n-Dlog4j2.disable.jmx=true\n-Dlog4j.skipJansi=true\n");
-            defaultMap
-                    .put(FessConfig.JVM_THUMBNAIL_OPTIONS,
-                            "-Djava.awt.headless=true\n-Dfile.encoding=UTF-8\n-Djna.nosys=true\n-Djdk.io.permissionsUseCanonicalPath=true\n-server\n-Xmx128m\n-XX:MaxMetaspaceSize=128m\n-XX:CompressedClassSpaceSize=32m\n-XX:-UseGCOverheadLimit\n-XX:+UseConcMarkSweepGC\n-XX:CMSInitiatingOccupancyFraction=75\n-XX:+UseCMSInitiatingOccupancyOnly\n-XX:+UseTLAB\n-XX:+DisableExplicitGC\n-XX:+HeapDumpOnOutOfMemoryError\n-XX:-OmitStackTraceInFastThrow\n-Djcifs.smb.client.responseTimeout=30000\n-Djcifs.smb.client.soTimeout=35000\n-Djcifs.smb.client.connTimeout=60000\n-Djcifs.smb.client.sessionTimeout=60000\n-Dgroovy.use.classvalue=true\n-Dio.netty.noUnsafe=true\n-Dio.netty.noKeySetOptimization=true\n-Dio.netty.recycler.maxCapacityPerThread=0\n-Dlog4j.shutdownHookEnabled=false\n-Dlog4j2.disable.jmx=true\n-Dlog4j.skipJansi=true\n-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider\n-Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true\n");
+            defaultMap.put(FessConfig.JVM_CRAWLER_OPTIONS, "-Djava.awt.headless=true\n-Dfile.encoding=UTF-8\n-Djna.nosys=true\n-Djdk.io.permissionsUseCanonicalPath=true\n-server\n-Xmx512m\n-XX:MaxMetaspaceSize=128m\n-XX:CompressedClassSpaceSize=32m\n-XX:-UseGCOverheadLimit\n-XX:+UseConcMarkSweepGC\n-XX:CMSInitiatingOccupancyFraction=75\n-XX:+UseCMSInitiatingOccupancyOnly\n-XX:+UseTLAB\n-XX:+DisableExplicitGC\n-XX:+HeapDumpOnOutOfMemoryError\n-XX:-OmitStackTraceInFastThrow\n-Djcifs.smb.client.responseTimeout=30000\n-Djcifs.smb.client.soTimeout=35000\n-Djcifs.smb.client.connTimeout=60000\n-Djcifs.smb.client.sessionTimeout=60000\n-Dgroovy.use.classvalue=true\n-Dio.netty.noUnsafe=true\n-Dio.netty.noKeySetOptimization=true\n-Dio.netty.recycler.maxCapacityPerThread=0\n-Dlog4j.shutdownHookEnabled=false\n-Dlog4j2.disable.jmx=true\n-Dlog4j.skipJansi=true\n-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider\n-Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true\n");
+            defaultMap.put(FessConfig.JVM_SUGGEST_OPTIONS, "-Djava.awt.headless=true\n-Dfile.encoding=UTF-8\n-Djna.nosys=true\n-Djdk.io.permissionsUseCanonicalPath=true\n-server\n-Xmx256m\n-XX:MaxMetaspaceSize=128m\n-XX:CompressedClassSpaceSize=32m\n-XX:-UseGCOverheadLimit\n-XX:+UseConcMarkSweepGC\n-XX:CMSInitiatingOccupancyFraction=75\n-XX:+UseCMSInitiatingOccupancyOnly\n-XX:+UseTLAB\n-XX:+DisableExplicitGC\n-XX:+HeapDumpOnOutOfMemoryError\n-Dgroovy.use.classvalue=true\n-Dio.netty.noUnsafe=true\n-Dio.netty.noKeySetOptimization=true\n-Dio.netty.recycler.maxCapacityPerThread=0\n-Dlog4j.shutdownHookEnabled=false\n-Dlog4j2.disable.jmx=true\n-Dlog4j.skipJansi=true\n");
+            defaultMap.put(FessConfig.JVM_THUMBNAIL_OPTIONS, "-Djava.awt.headless=true\n-Dfile.encoding=UTF-8\n-Djna.nosys=true\n-Djdk.io.permissionsUseCanonicalPath=true\n-server\n-Xmx128m\n-XX:MaxMetaspaceSize=128m\n-XX:CompressedClassSpaceSize=32m\n-XX:-UseGCOverheadLimit\n-XX:+UseConcMarkSweepGC\n-XX:CMSInitiatingOccupancyFraction=75\n-XX:+UseCMSInitiatingOccupancyOnly\n-XX:+UseTLAB\n-XX:+DisableExplicitGC\n-XX:+HeapDumpOnOutOfMemoryError\n-XX:-OmitStackTraceInFastThrow\n-Djcifs.smb.client.responseTimeout=30000\n-Djcifs.smb.client.soTimeout=35000\n-Djcifs.smb.client.connTimeout=60000\n-Djcifs.smb.client.sessionTimeout=60000\n-Dgroovy.use.classvalue=true\n-Dio.netty.noUnsafe=true\n-Dio.netty.noKeySetOptimization=true\n-Dio.netty.recycler.maxCapacityPerThread=0\n-Dlog4j.shutdownHookEnabled=false\n-Dlog4j2.disable.jmx=true\n-Dlog4j.skipJansi=true\n-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider\n-Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true\n");
             defaultMap.put(FessConfig.JOB_SYSTEM_JOB_IDS, "default_crawler");
             defaultMap.put(FessConfig.JOB_TEMPLATE_TITLE_WEB, "Web Crawler - {0}");
             defaultMap.put(FessConfig.JOB_TEMPLATE_TITLE_FILE, "File Crawler - {0}");
             defaultMap.put(FessConfig.JOB_TEMPLATE_TITLE_DATA, "Data Crawler - {0}");
-            defaultMap
-                    .put(FessConfig.JOB_TEMPLATE_SCRIPT,
-                            "return container.getComponent(\"crawlJob\").logLevel(\"info\").sessionId(\"{3}\").webConfigIds([{0}] as String[]).fileConfigIds([{1}] as String[]).dataConfigIds([{2}] as String[]).jobExecutor(executor).execute();");
+            defaultMap.put(FessConfig.JOB_TEMPLATE_SCRIPT, "return container.getComponent(\"crawlJob\").logLevel(\"info\").sessionId(\"{3}\").webConfigIds([{0}] as String[]).fileConfigIds([{1}] as String[]).dataConfigIds([{2}] as String[]).jobExecutor(executor).execute();");
             defaultMap.put(FessConfig.JAVA_COMMAND_PATH, "java");
             defaultMap.put(FessConfig.PATH_ENCODING, "UTF-8");
             defaultMap.put(FessConfig.USE_OWN_TMP_DIR, "true");
@@ -8465,9 +8457,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             defaultMap.put(FessConfig.SUPPORTED_UPLOADED_CSS_EXTENTIONS, "css");
             defaultMap.put(FessConfig.SUPPORTED_UPLOADED_MEDIA_EXTENTIONS, "jpg,jpeg,gif,png,swf");
             defaultMap.put(FessConfig.SUPPORTED_UPLOADED_FILES, "license.properties");
-            defaultMap
-                    .put(FessConfig.SUPPORTED_LANGUAGES,
-                            "ar,bg,bn,ca,ckb_IQ,cs,da,de,el,en,en_IE,es,et,eu,fa,fi,fr,gl,gu,he,hi,hr,hu,hy,id,it,ja,ko,lt,lv,mk,ml,nl,no,pa,pl,pt,pt_BR,ro,ru,si,sq,sv,ta,te,th,tl,tr,uk,ur,vi,zh_CN,zh_TW,zh");
+            defaultMap.put(FessConfig.SUPPORTED_LANGUAGES, "ar,bg,bn,ca,ckb_IQ,cs,da,de,el,en,en_IE,es,et,eu,fa,fi,fr,gl,gu,he,hi,hr,hu,hy,id,it,ja,ko,lt,lv,mk,ml,nl,no,pa,pl,pt,pt_BR,ro,ru,si,sq,sv,ta,te,th,tl,tr,uk,ur,vi,zh_CN,zh_TW,zh");
             defaultMap.put(FessConfig.API_ACCESS_TOKEN_LENGTH, "60");
             defaultMap.put(FessConfig.API_ACCESS_TOKEN_REQUIRED, "false");
             defaultMap.put(FessConfig.API_ACCESS_TOKEN_REQUEST_PARAMETER, "");
@@ -8487,9 +8477,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             defaultMap.put(FessConfig.CRAWLER_DOCUMENT_MAX_ALPHANUM_TERM_SIZE, "20");
             defaultMap.put(FessConfig.CRAWLER_DOCUMENT_MAX_SYMBOL_TERM_SIZE, "10");
             defaultMap.put(FessConfig.CRAWLER_DOCUMENT_DUPLICATE_TERM_REMOVED, "false");
-            defaultMap
-                    .put(FessConfig.CRAWLER_DOCUMENT_SPACE_CHARS,
-                            "u0009u000Au000Bu000Cu000Du001Cu001Du001Eu001Fu0020u00A0u1680u180Eu2000u2001u2002u2003u2004u2005u2006u2007u2008u2009u200Au200Bu200Cu202Fu205Fu3000uFEFFuFFFDu00B6");
+            defaultMap.put(FessConfig.CRAWLER_DOCUMENT_SPACE_CHARS, "u0009u000Au000Bu000Cu000Du001Cu001Du001Eu001Fu0020u00A0u1680u180Eu2000u2001u2002u2003u2004u2005u2006u2007u2008u2009u200Au200Bu200Cu202Fu205Fu3000uFEFFuFFFDu00B6");
             defaultMap.put(FessConfig.CRAWLER_DOCUMENT_FULLSTOP_CHARS, "u002eu06d4u2e3cu3002");
             defaultMap.put(FessConfig.CRAWLER_CRAWLING_DATA_ENCODING, "UTF-8");
             defaultMap.put(FessConfig.CRAWLER_WEB_PROTOCOLS, "http,https");
@@ -8637,9 +8625,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             defaultMap.put(FessConfig.QUERY_DEFAULT_LANGUAGES, "");
             defaultMap.put(FessConfig.QUERY_JSON_DEFAULT_PREFERENCE, "_query");
             defaultMap.put(FessConfig.QUERY_GSA_DEFAULT_PREFERENCE, "_query");
-            defaultMap
-                    .put(FessConfig.QUERY_LANGUAGE_MAPPING,
-                            "ar=ar\nbg=bg\nbn=bn\nca=ca\nckb-iq=ckb-iq\nckb_IQ=ckb-iq\ncs=cs\nda=da\nde=de\nel=el\nen=en\nen-ie=en-ie\nen_IE=en-ie\nes=es\net=et\neu=eu\nfa=fa\nfi=fi\nfr=fr\ngl=gl\ngu=gu\nhe=he\nhi=hi\nhr=hr\nhu=hu\nhy=hy\nid=id\nit=it\nja=ja\nko=ko\nlt=lt\nlv=lv\nmk=mk\nml=ml\nnl=nl\nno=no\npa=pa\npl=pl\npt=pt\npt-br=pt-br\npt_BR=pt-br\nro=ro\nru=ru\nsi=si\nsq=sq\nsv=sv\nta=ta\nte=te\nth=th\ntl=tl\ntr=tr\nuk=uk\nur=ur\nvi=vi\nzh-cn=zh-cn\nzh_CN=zh-cn\nzh-tw=zh-tw\nzh_TW=zh-tw\nzh=zh\n");
+            defaultMap.put(FessConfig.QUERY_LANGUAGE_MAPPING, "ar=ar\nbg=bg\nbn=bn\nca=ca\nckb-iq=ckb-iq\nckb_IQ=ckb-iq\ncs=cs\nda=da\nde=de\nel=el\nen=en\nen-ie=en-ie\nen_IE=en-ie\nes=es\net=et\neu=eu\nfa=fa\nfi=fi\nfr=fr\ngl=gl\ngu=gu\nhe=he\nhi=hi\nhr=hr\nhu=hu\nhy=hy\nid=id\nit=it\nja=ja\nko=ko\nlt=lt\nlv=lv\nmk=mk\nml=ml\nnl=nl\nno=no\npa=pa\npl=pl\npt=pt\npt-br=pt-br\npt_BR=pt-br\nro=ro\nru=ru\nsi=si\nsq=sq\nsv=sv\nta=ta\nte=te\nth=th\ntl=tl\ntr=tr\nuk=uk\nur=ur\nvi=vi\nzh-cn=zh-cn\nzh_CN=zh-cn\nzh-tw=zh-tw\nzh_TW=zh-tw\nzh=zh\n");
             defaultMap.put(FessConfig.QUERY_BOOST_TITLE, "0.2");
             defaultMap.put(FessConfig.QUERY_BOOST_TITLE_LANG, "1.0");
             defaultMap.put(FessConfig.QUERY_BOOST_CONTENT, "0.1");
@@ -8651,9 +8637,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             defaultMap.put(FessConfig.INDEX_BACKUP_TARGETS, ".fess_basic_config.bulk,.fess_config.bulk,.fess_user.bulk,system.properties");
             defaultMap.put(FessConfig.INDEX_BACKUP_LOG_TARGETS, "click_log.ndjson,favorite_log.ndjson,search_log.ndjson,user_info.ndjson");
             defaultMap.put(FessConfig.LOGGING_SEARCH_DOCS_ENABLED, "true");
-            defaultMap
-                    .put(FessConfig.LOGGING_SEARCH_DOCS_FIELDS,
-                            "filetype,created,click_count,title,doc_id,url,score,site,filename,host,digest,boost,mimetype,favorite_count,_id,lang,last_modified,content_length,timestamp");
+            defaultMap.put(FessConfig.LOGGING_SEARCH_DOCS_FIELDS, "filetype,created,click_count,title,doc_id,url,score,site,filename,host,digest,boost,mimetype,favorite_count,_id,lang,last_modified,content_length,timestamp");
             defaultMap.put(FessConfig.FORM_ADMIN_MAX_INPUT_SIZE, "4000");
             defaultMap.put(FessConfig.AUTHENTICATION_ADMIN_USERS, "admin");
             defaultMap.put(FessConfig.AUTHENTICATION_ADMIN_ROLES, "admin");
@@ -8742,6 +8726,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             defaultMap.put(FessConfig.ONLINE_HELP_NAME_DICT, "dict");
             defaultMap.put(FessConfig.ONLINE_HELP_NAME_DICT_KUROMOJI, "kuromoji");
             defaultMap.put(FessConfig.ONLINE_HELP_NAME_DICT_PROTWORDS, "protwords");
+            defaultMap.put(FessConfig.ONLINE_HELP_NAME_DICT_STOPWORDS, "stopwords");
             defaultMap.put(FessConfig.ONLINE_HELP_NAME_DICT_MAPPING, "mapping");
             defaultMap.put(FessConfig.ONLINE_HELP_NAME_WEBCONFIG, "webconfig");
             defaultMap.put(FessConfig.ONLINE_HELP_NAME_SEARCHLIST, "searchlist");

+ 0 - 28
src/main/resources/esclient.xml

@@ -360,10 +360,6 @@
 			<arg>"fess"</arg>
 			<arg>"eu/stemmer_override.txt"</arg>
 		</postConstruct>
-		<postConstruct name="addConfigFile">
-			<arg>"fess"</arg>
-			<arg>"fa/stemmer_override.txt"</arg>
-		</postConstruct>
 		<postConstruct name="addConfigFile">
 			<arg>"fess"</arg>
 			<arg>"fi/stemmer_override.txt"</arg>
@@ -396,14 +392,6 @@
 			<arg>"fess"</arg>
 			<arg>"it/stemmer_override.txt"</arg>
 		</postConstruct>
-		<postConstruct name="addConfigFile">
-			<arg>"fess"</arg>
-			<arg>"ja/stemmer_override.txt"</arg>
-		</postConstruct>
-		<postConstruct name="addConfigFile">
-			<arg>"fess"</arg>
-			<arg>"ko/stemmer_override.txt"</arg>
-		</postConstruct>
 		<postConstruct name="addConfigFile">
 			<arg>"fess"</arg>
 			<arg>"lt/stemmer_override.txt"</arg>
@@ -440,26 +428,10 @@
 			<arg>"fess"</arg>
 			<arg>"sv/stemmer_override.txt"</arg>
 		</postConstruct>
-		<postConstruct name="addConfigFile">
-			<arg>"fess"</arg>
-			<arg>"th/stemmer_override.txt"</arg>
-		</postConstruct>
 		<postConstruct name="addConfigFile">
 			<arg>"fess"</arg>
 			<arg>"tr/stemmer_override.txt"</arg>
 		</postConstruct>
-		<postConstruct name="addConfigFile">
-			<arg>"fess"</arg>
-			<arg>"vi/stemmer_override.txt"</arg>
-		</postConstruct>
-		<postConstruct name="addConfigFile">
-			<arg>"fess"</arg>
-			<arg>"zh-cn/stemmer_override.txt"</arg>
-		</postConstruct>
-		<postConstruct name="addConfigFile">
-			<arg>"fess"</arg>
-			<arg>"zh-tw/stemmer_override.txt"</arg>
-		</postConstruct>
 		<!-- fess index -->
 		<postConstruct name="addIndexConfig">
 			<arg>"fess/doc"</arg>

+ 1 - 0
src/main/resources/fess_config.properties

@@ -546,6 +546,7 @@ online.help.name.dict.synonym=synonym
 online.help.name.dict=dict
 online.help.name.dict.kuromoji=kuromoji
 online.help.name.dict.protwords=protwords
+online.help.name.dict.stopwords=stopwords
 online.help.name.dict.mapping=mapping
 online.help.name.webconfig=webconfig
 online.help.name.searchlist=searchlist

+ 6 - 0
src/main/resources/fess_dict.xml

@@ -14,6 +14,9 @@
 		<postConstruct name="addCreator">
 			<arg>protwordsCreator</arg>
 		</postConstruct>
+		<postConstruct name="addCreator">
+			<arg>stopwordsCreator</arg>
+		</postConstruct>
 		<postConstruct name="addCreator">
 			<arg>charMappingCreator</arg>
 		</postConstruct>
@@ -28,6 +31,9 @@
 	<component name="protwordsCreator"
 		class="org.codelibs.fess.dict.protwords.ProtwordsCreator">
 	</component>
+	<component name="stopwordsCreator"
+		class="org.codelibs.fess.dict.stopwords.StopwordsCreator">
+	</component>
 	<component name="charMappingCreator"
 		class="org.codelibs.fess.dict.mapping.CharMappingCreator">
 	</component>

+ 20 - 2
src/main/resources/fess_indices/fess.json

@@ -440,6 +440,14 @@
             "連体詞"
           ]
         },
+        "japanese_stop": {
+          "type":       "stop",
+          "stopwords_path": "${fess.dictionary.path}ja/stopwords.txt"
+        },
+        "korean_stop": {
+          "type":       "stop",
+          "stopwords_path": "${fess.dictionary.path}ko/stopwords.txt"
+        },
         "latvian_stop": {
           "type":       "stop",
           "stopwords_path": "${fess.dictionary.path}lv/stopwords.txt"
@@ -492,6 +500,10 @@
           "type":       "stop",
           "stopwords_path": "${fess.dictionary.path}fa/stopwords.txt"
         },
+        "persian_keywords": {
+          "type":       "keyword_marker",
+          "keywords_path": "${fess.dictionary.path}fa/protwords.txt"
+        },
         "portuguese_stop": {
           "type":       "stop",
           "stopwords_path": "${fess.dictionary.path}pt/stopwords.txt"
@@ -596,6 +608,10 @@
           "type":       "stop",
           "stopwords_path": "${fess.dictionary.path}th/stopwords.txt"
         },
+        "thai_keywords": {
+          "type":       "keyword_marker",
+          "keywords_path": "${fess.dictionary.path}th/protwords.txt"
+        },
         "traditional_chinese_stop": {
           "type":       "stop",
           "stopwords_path": "${fess.dictionary.path}zh-tw/stopwords.txt"
@@ -933,7 +949,8 @@
             "fess_japanese_baseform",
             "fess_japanese_stemmer",
             "japanese_pos_filter",
-            "lowercase"
+            "lowercase",
+            "japanese_stop"
           ]
         },
         "korean_analyzer": {
@@ -941,7 +958,8 @@
           "tokenizer":"korean_tokenizer",
           "filter": [
             "truncate10_filter",
-            "lowercase"
+            "lowercase",
+            "korean_stop"
           ]
         },
         "latvian_analyzer": {

+ 0 - 0
src/main/resources/fess_indices/fess/ja/stopwords.txt


+ 0 - 0
src/main/resources/fess_indices/fess/ko/stopwords.txt


+ 18 - 5
src/main/resources/fess_label.properties

@@ -198,8 +198,8 @@ labels.prev_page=Prev
 labels.next_page=Next
 labels.did_not_match=Your search - <b>{0}</b> - did not match any documents.
 labels.search_title=Fess
-labels.search_popular_word_word=Popular Words: 
-labels.search_related_queries=Related Words: 
+labels.search_popular_word_word=Popular Words:
+labels.search_related_queries=Related Words:
 labels.search_result_select_sort=-- Sort --
 labels.search_result_select_num=-- Results per page --
 labels.search_result_sort_score_desc=Score
@@ -217,8 +217,8 @@ labels.search_result_sort_favorite_count_asc=Favorite (ascending)
 labels.search_result_sort_favorite_count_desc=Favorite (descending)
 labels.search_result_sort_multiple=Multiple
 labels.search_result_size={0} bytes
-labels.search_result_created=Registered: 
-labels.search_result_last_modified=Last Modified: 
+labels.search_result_created=Registered:
+labels.search_result_last_modified=Last Modified:
 labels.search_result_favorite=Like
 labels.search_result_favorited=Liked
 labels.search_click_count=Viewed ({0})
@@ -667,6 +667,19 @@ labels.dict_protwords_source = Source
 labels.dict_protwords_button_download = Download
 labels.dict_protwords_button_upload = Upload
 labels.dict_protwords_file = Protwords File
+labels.dict_stopwords_configuration=Stopwords List
+labels.dict_stopwords_title = Stopwords List
+labels.dict_stopwords_list_link = List
+labels.dict_stopwords_link_create = Create New
+labels.dict_stopwords_link_edit = Edit
+labels.dict_stopwords_link_delete = Delete
+labels.dict_stopwords_link_details = Details
+labels.dict_stopwords_link_download = Download
+labels.dict_stopwords_link_upload = Upload
+labels.dict_stopwords_source = Source
+labels.dict_stopwords_button_download = Download
+labels.dict_stopwords_button_upload = Upload
+labels.dict_stopwords_file = Stopwords File
 labels.boost_document_rule_configuration=Doc Boost
 labels.boost_document_rule_title_details=Doc Boost
 labels.boost_document_rule_list_url_expr=Condition
@@ -872,7 +885,7 @@ labels.esreq_request_file=Request File
 labels.requestFile=Request File
 labels.esreq_button_upload=Send
 labels.facet_is_not_found=No match
-labels.doc_score=Score: 
+labels.doc_score=Score:
 labels.development_mode_warning=Running as Development mode. For production use, please install a standalone elasticsearch server.
 labels.advance=Advance
 labels.advance_search_title=Advanced Search

+ 14 - 1
src/main/resources/fess_label_de.properties

@@ -664,6 +664,19 @@ labels.dict_protwords_source = Source
 labels.dict_protwords_button_download = Download
 labels.dict_protwords_button_upload = Upload
 labels.dict_protwords_file = Protwords File
+labels.dict_stopwords_configuration=Stopwords List
+labels.dict_stopwords_title = Stopwords List
+labels.dict_stopwords_list_link = List
+labels.dict_stopwords_link_create = Create New
+labels.dict_stopwords_link_edit = Edit
+labels.dict_stopwords_link_delete = Delete
+labels.dict_stopwords_link_details = Details
+labels.dict_stopwords_link_download = Download
+labels.dict_stopwords_link_upload = Upload
+labels.dict_stopwords_source = Source
+labels.dict_stopwords_button_download = Download
+labels.dict_stopwords_button_upload = Upload
+labels.dict_stopwords_file = Stopwords File
 labels.boost_document_rule_configuration=Doc Boost
 labels.boost_document_rule_title_details=Doc Boost
 labels.boost_document_rule_list_url_expr=Condition
@@ -868,5 +881,5 @@ labels.esreq_request_file=Request File
 labels.requestFile=Request File
 labels.esreq_button_upload=Send
 labels.facet_is_not_found=No match.
-labels.doc_score=Score: 
+labels.doc_score=Score:
 labels.development_mode_warning=Running as Development mode. For production use, please install a standalone elasticsearch server.

+ 18 - 5
src/main/resources/fess_label_en.properties

@@ -198,8 +198,8 @@ labels.prev_page=Prev
 labels.next_page=Next
 labels.did_not_match=Your search - <b>{0}</b> - did not match any documents.
 labels.search_title=Fess
-labels.search_popular_word_word=Popular Words: 
-labels.search_related_queries=Related Words: 
+labels.search_popular_word_word=Popular Words:
+labels.search_related_queries=Related Words:
 labels.search_result_select_sort=-- Sort --
 labels.search_result_select_num=-- Results per page --
 labels.search_result_sort_score_desc=Score
@@ -217,8 +217,8 @@ labels.search_result_sort_favorite_count_asc=Favorite (ascending)
 labels.search_result_sort_favorite_count_desc=Favorite (descending)
 labels.search_result_sort_multiple=Multiple
 labels.search_result_size={0} bytes
-labels.search_result_created=Registered: 
-labels.search_result_last_modified=Last Modified: 
+labels.search_result_created=Registered:
+labels.search_result_last_modified=Last Modified:
 labels.search_result_favorite=Like
 labels.search_result_favorited=Liked
 labels.search_click_count=Viewed ({0})
@@ -667,6 +667,19 @@ labels.dict_protwords_source = Source
 labels.dict_protwords_button_download = Download
 labels.dict_protwords_button_upload = Upload
 labels.dict_protwords_file = Protwords File
+labels.dict_stopwords_configuration=Stopwords List
+labels.dict_stopwords_title = Stopwords List
+labels.dict_stopwords_list_link = List
+labels.dict_stopwords_link_create = Create New
+labels.dict_stopwords_link_edit = Edit
+labels.dict_stopwords_link_delete = Delete
+labels.dict_stopwords_link_details = Details
+labels.dict_stopwords_link_download = Download
+labels.dict_stopwords_link_upload = Upload
+labels.dict_stopwords_source = Source
+labels.dict_stopwords_button_download = Download
+labels.dict_stopwords_button_upload = Upload
+labels.dict_stopwords_file = Stopwords File
 labels.boost_document_rule_configuration=Doc Boost
 labels.boost_document_rule_title_details=Doc Boost
 labels.boost_document_rule_list_url_expr=Condition
@@ -872,7 +885,7 @@ labels.esreq_request_file=Request File
 labels.requestFile=Request File
 labels.esreq_button_upload=Send
 labels.facet_is_not_found=No match.
-labels.doc_score=Score: 
+labels.doc_score=Score:
 labels.development_mode_warning=Running as Development mode. For production use, please install a standalone elasticsearch server.
 labels.advance=Advance
 labels.advance_search_title=Advanced Search

+ 29 - 16
src/main/resources/fess_label_ja.properties

@@ -183,33 +183,33 @@ labels.sidebar.placeholder_search=検索...
 labels.sidebar.menu=メニュー
 labels.footer.copyright=&copy;2018 <a href="https://github.com/codelibs">CodeLibs Project</a>.
 labels.search=検索
-labels.similar_doc_result_status=類似している結果を表示しています。 
+labels.similar_doc_result_status=類似している結果を表示しています。
 labels.search_result_status=<b>{0}</b> の検索結果<span class="br-phone"></span> <b>{1}</b> 件中 <b>{2}</b> - <b>{3}</b> 件目
 labels.search_result_time=({0} 秒)
 labels.prev_page=前へ
 labels.next_page=次へ
 labels.did_not_match=<b>{0}</b> に一致する情報は見つかりませんでした。
 labels.search_title=Fess
-labels.search_popular_word_word=人気ワード: 
-labels.search_related_queries=関連ワード: 
+labels.search_popular_word_word=人気ワード:
+labels.search_related_queries=関連ワード:
 labels.search_result_select_sort=-  ソート  -
 labels.search_result_select_num=- 表示件数 -
 labels.search_result_sort_score_desc=スコア順
 labels.search_result_sort_filename_asc=ファイル名 (昇順)
 labels.search_result_sort_filename_desc=ファイル名 (降順)
-labels.search_result_sort_created_asc=日付 (昇順) 
-labels.search_result_sort_created_desc=日付 (降順) 
-labels.search_result_sort_content_length_asc=サイズ (昇順) 
-labels.search_result_sort_content_length_desc=サイズ (降順) 
-labels.search_result_sort_last_modified_asc=最終更新日時 (昇順) 
-labels.search_result_sort_last_modified_desc=最終更新日時 (降順) 
-labels.search_result_sort_click_count_asc=クリック数 (昇順) 
-labels.search_result_sort_click_count_desc=クリック数 (降順) 
-labels.search_result_sort_favorite_count_asc=お気に入り数 (昇順) 
-labels.search_result_sort_favorite_count_desc=お気に入り数 (降順) 
+labels.search_result_sort_created_asc=日付 (昇順)
+labels.search_result_sort_created_desc=日付 (降順)
+labels.search_result_sort_content_length_asc=サイズ (昇順)
+labels.search_result_sort_content_length_desc=サイズ (降順)
+labels.search_result_sort_last_modified_asc=最終更新日時 (昇順)
+labels.search_result_sort_last_modified_desc=最終更新日時 (降順)
+labels.search_result_sort_click_count_asc=クリック数 (昇順)
+labels.search_result_sort_click_count_desc=クリック数 (降順)
+labels.search_result_sort_favorite_count_asc=お気に入り数 (昇順)
+labels.search_result_sort_favorite_count_desc=お気に入り数 (降順)
 labels.search_result_sort_multiple=複数
 labels.search_result_size={0} バイト
-labels.search_result_created=登録日時: 
+labels.search_result_created=登録日時:
 labels.search_result_last_modified=最終更新日時:
 labels.search_result_favorite=Like
 labels.search_result_favorited=Liked
@@ -480,7 +480,7 @@ labels.design_configuration=ページのデザイン
 labels.design_title_file_upload=アップロードするファイル
 labels.design_title_file=ファイルマネージャー
 labels.design_file=ファイルのアップロード
-labels.design_file_name=ファイル名 (オプション) 
+labels.design_file_name=ファイル名 (オプション)
 labels.design_button_upload=アップロード
 labels.design_file_title_edit=ページファイルの表示
 labels.design_edit_button=編集
@@ -661,6 +661,19 @@ labels.dict_protwords_source = 単語情報
 labels.dict_protwords_button_download = ダウンロード
 labels.dict_protwords_button_upload = アップロード
 labels.dict_protwords_file = Protwordsファイル
+labels.dict_stopwords_configuration=Stopwords単語一覧
+labels.dict_stopwords_title = Stopwords単語一覧
+labels.dict_stopwords_list_link = 一覧
+labels.dict_stopwords_link_create = 新規作成
+labels.dict_stopwords_link_edit = 編集
+labels.dict_stopwords_link_delete = 削除
+labels.dict_stopwords_link_details = 詳細
+labels.dict_stopwords_link_download = ダウンロード
+labels.dict_stopwords_link_upload = アップロード
+labels.dict_stopwords_source = 単語情報
+labels.dict_stopwords_button_download = ダウンロード
+labels.dict_stopwords_button_upload = アップロード
+labels.dict_stopwords_file = Stopwordsファイル
 labels.boost_document_rule_configuration=ドキュメントブースト
 labels.boost_document_rule_title_details=ドキュメントブースト
 labels.boost_document_rule_list_url_expr=状態
@@ -874,7 +887,7 @@ labels.esreq_request_file=リクエストファイル
 labels.requestFile=リクエストファイル
 labels.esreq_button_upload=送信
 labels.facet_is_not_found=該当なし
-labels.doc_score=スコア: 
+labels.doc_score=スコア:
 labels.development_mode_warning=開発モードで起動しています。本運用環境ではElasticsearchを別途インストールしてください。
 labels.advance=詳細検索
 labels.advance_search_title=詳細検索

+ 15 - 2
src/main/resources/fess_label_ko.properties

@@ -185,8 +185,8 @@ labels.next_page = 다음
 labels.did_not_match = <b> {0} </b>에 일치하는 정보를 찾지 못했습니다.
 labels.search_title = Fess
 labels.search_popular_word_word = 인기 검색어:
-labels.search_result_select_sort =-- 정렬 -- 
-labels.search_result_select_num =-- 표시 건수 -- 
+labels.search_result_select_sort =-- 정렬 --
+labels.search_result_select_num =-- 표시 건수 --
 labels.search_result_sort_score_desc = 점수 순
 labels.search_result_sort_filename_asc = 파일 이름 (오름차순)
 labels.search_result_sort_filename_desc = 파일 이름 (내림차순)
@@ -646,6 +646,19 @@ labels.dict_protwords_source = 단어 정보
 labels.dict_protwords_button_download = 다운로드
 labels.dict_protwords_button_upload = 업로드
 labels.dict_protwords_file = Protwords 파일
+labels.dict_stopwords_configuration = Stopwords 단어 목록
+labels.dict_stopwords_title = Stopwords 단어 목록
+labels.dict_stopwords_list_link = 목록
+labels.dict_stopwords_link_create = 새로 만들기
+labels.dict_stopwords_link_edit = 편집
+labels.dict_stopwords_link_delete = 삭제
+labels.dict_stopwords_link_details = 상세
+labels.dict_stopwords_link_download = 다운로드
+labels.dict_stopwords_link_upload = 업로드
+labels.dict_stopwords_source = 단어 정보
+labels.dict_stopwords_button_download = 다운로드
+labels.dict_stopwords_button_upload = 업로드
+labels.dict_stopwords_file = Stopwords 파일
 labels.boost_document_rule_configuration = 문서 부스트
 labels.boost_document_rule_title_details = 문서 부스트
 labels.boost_document_rule_list_url_expr = 상태

+ 17 - 4
src/main/resources/fess_label_ru.properties

@@ -44,7 +44,7 @@ labels.parameters=Параметры
 labels.designFile=Загрузить файл
 labels.bulkFile=Большой файл
 labels.appendQueryParameter=Дополнительные параметры запроса
-labels.configId=Настройки ID 
+labels.configId=Настройки ID
 labels.configParameter=Настройки параметров
 labels.content=Содержание
 labels.csvFileEncoding=CSV кодировка
@@ -188,7 +188,7 @@ labels.prev_page=Назад
 labels.next_page=Вперед
 labels.did_not_match=По запросу - <b>{0}</b> - совпадений не найдено.
 labels.search_title=Fess
-labels.search_popular_word_word=Популярные слова: 
+labels.search_popular_word_word=Популярные слова:
 labels.search_result_select_sort=-- Сортировка --
 labels.search_result_select_num=-- Результатов на страницу --
 labels.search_result_sort_score_desc=Оценка
@@ -204,8 +204,8 @@ labels.search_result_sort_favorite_count_asc=Избранные (ascending)
 labels.search_result_sort_favorite_count_desc=Избранные (по убыванию)
 labels.search_result_sort_multiple=Множественный
 labels.search_result_size={0} байтов
-labels.search_result_created=Записан: 
-labels.search_result_last_modified=Дата изменения: 
+labels.search_result_created=Записан:
+labels.search_result_last_modified=Дата изменения:
 labels.search_result_favorite=Like
 labels.search_result_favorited=Liked
 labels.search_click_count=Просмотрено ({0})
@@ -647,6 +647,19 @@ labels.dict_protwords_source = Source
 labels.dict_protwords_button_download = Download
 labels.dict_protwords_button_upload = Upload
 labels.dict_protwords_file = Protwords File
+labels.dict_stopwords_configuration=Stopwords List
+labels.dict_stopwords_title = Stopwords List
+labels.dict_stopwords_list_link = List
+labels.dict_stopwords_link_create = Создать новый
+labels.dict_stopwords_link_edit = Изменить
+labels.dict_stopwords_link_delete = Удалить
+labels.dict_stopwords_link_details = Details
+labels.dict_stopwords_link_download = Download
+labels.dict_stopwords_link_upload = Upload
+labels.dict_stopwords_source = Source
+labels.dict_stopwords_button_download = Download
+labels.dict_stopwords_button_upload = Upload
+labels.dict_stopwords_file = Stopwords File
 labels.boost_document_rule_configuration=Doc Boost
 labels.boost_document_rule_title_details=Doc Boost
 labels.boost_document_rule_list_url_expr=Condition

+ 2 - 0
src/main/resources/fess_message.properties

@@ -109,6 +109,8 @@ errors.failed_to_download_kuromoji_file=Failed to download the Kuromoji file.
 errors.failed_to_upload_kuromoji_file=Failed to upload the Kuromoji file.
 errors.failed_to_download_protwords_file=Failed to download the Protwords file.
 errors.failed_to_upload_protwords_file=Failed to upload the Protwords file.
+errors.failed_to_download_stopwords_file=Failed to download the Stopwords file.
+errors.failed_to_upload_stopwords_file=Failed to upload the Stopwords file.
 errors.failed_to_download_elevate_file=Failed to download the Elevate file.
 errors.failed_to_upload_elevate_file=Failed to upload the Elevate file.
 errors.failed_to_download_badword_file=Failed to download the Badword file.

+ 154 - 0
src/main/webapp/WEB-INF/view/admin/dict/stopwords/admin_dict_stopwords.jsp

@@ -0,0 +1,154 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message
+		key="labels.dict_stopwords_configuration" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+</head>
+<body class="hold-transition skin-blue sidebar-mini">
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="dict" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.dict_stopwords_title" />
+				</h1>
+				<ol class="breadcrumb">
+					<li><la:link href="list">
+							<la:message key="labels.dict_list_link" />
+						</la:link></li>
+					<li><la:message key="labels.dict_stopwords_list_link" /></li>
+				</ol>
+			</section>
+			<section class="content">
+				<div class="row">
+					<div class="col-md-12">
+						<div class="box box-primary">
+							<div class="box-header with-border">
+								<h3 class="box-title">
+									<la:message key="labels.dict_stopwords_list_link" />
+								</h3>
+								<div class="btn-group pull-right">
+									<la:link href="/admin/dict" styleClass="btn btn-default btn-xs">
+										<i class="fa fa-book"></i>
+										<la:message key="labels.dict_list_link" />
+									</la:link>
+									<la:link href="list/1?dictId=${f:u(dictId)}"
+										styleClass="btn btn-primary btn-xs">
+										<i class="fa fa-th-list"></i>
+										<la:message key="labels.dict_stopwords_list_link" />
+									</la:link>
+									<la:link href="createnew/${f:u(dictId)}"
+										styleClass="btn btn-success btn-xs">
+										<i class="fa fa-plus"></i>
+										<la:message key="labels.dict_stopwords_link_create" />
+									</la:link>
+									<la:link href="downloadpage/${f:u(dictId)}"
+										styleClass="btn btn-primary btn-xs">
+										<i class="fa fa-download"></i>
+										<la:message key="labels.dict_stopwords_link_download" />
+									</la:link>
+									<la:link href="uploadpage/${f:u(dictId)}"
+										styleClass="btn btn-success btn-xs">
+										<i class="fa fa-upload"></i>
+										<la:message key="labels.dict_stopwords_link_upload" />
+									</la:link>
+								</div>
+							</div>
+							<!-- /.box-header -->
+							<div class="box-body">
+								<%-- Message --%>
+								<div>
+									<la:info id="msg" message="true">
+										<div class="alert alert-info">${msg}</div>
+									</la:info>
+									<la:errors />
+								</div>
+								<%-- List --%>
+								<c:if test="${stopwordsPager.allRecordCount == 0}">
+									<div class="row top10">
+										<div class="col-sm-12">
+											<i class="fa fa-info-circle text-light-blue"></i>
+											<la:message key="labels.list_could_not_find_crud_table" />
+										</div>
+									</div>
+								</c:if>
+								<c:if test="${stopwordsPager.allRecordCount > 0}">
+									<div class="row">
+										<div class="col-sm-12">
+											<table class="table table-bordered table-striped">
+												<thead>
+													<tr>
+														<th><la:message key="labels.dict_stopwords_source" /></th>
+													</tr>
+												</thead>
+												<tbody>
+													<c:forEach var="data" varStatus="s"
+														items="${stopwordsItemItems}">
+														<tr
+															data-href="${contextPath}/admin/dict/stopwords/details/${f:u(dictId)}/4/${f:u(data.id)}">
+															<td>${f:h(data.inputValue)}</td>
+														</tr>
+													</c:forEach>
+												</tbody>
+											</table>
+										</div>
+									</div>
+									<c:set var="pager" value="${stopwordsPager}" scope="request" />
+									<div class="row">
+										<div class="col-sm-2">
+											<la:message key="labels.pagination_page_guide_msg"
+												arg0="${f:h(pager.currentPageNumber)}"
+												arg1="${f:h(pager.allPageCount)}"
+												arg2="${f:h(pager.allRecordCount)}" />
+										</div>
+										<div class="col-sm-10">
+											<ul class="pagination pagination-sm no-margin pull-right">
+												<c:if test="${pager.existPrePage}">
+													<li class="prev"><la:link
+															href="list/${pager.currentPageNumber - 1}?dictId=${f:u(dictId)}">
+															<la:message key="labels.prev_page" />
+														</la:link></li>
+												</c:if>
+												<c:if test="${!pager.existPrePage}">
+													<li class="prev disabled"><a href="#"><la:message
+																key="labels.prev_page" /></a></li>
+												</c:if>
+												<c:forEach var="p" varStatus="s"
+													items="${pager.pageNumberList}">
+													<li
+														<c:if test="${p == pager.currentPageNumber}">class="active"</c:if>><la:link
+															href="list/${p}?dictId=${f:u(dictId)}">${p}</la:link></li>
+												</c:forEach>
+												<c:if test="${pager.existNextPage}">
+													<li class="next"><la:link
+															href="list/${pager.currentPageNumber + 1}?dictId=${f:u(dictId)}">
+															<la:message key="labels.next_page" />
+														</la:link></li>
+												</c:if>
+												<c:if test="${!pager.existNextPage}">
+													<li class="next disabled"><a href="#"><la:message
+																key="labels.next_page" /></a></li>
+												</c:if>
+											</ul>
+										</div>
+									</div>
+								</c:if>
+							</div>
+							<!-- /.box-body -->
+						</div>
+						<!-- /.box -->
+					</div>
+				</div>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+</body>
+</html>

+ 123 - 0
src/main/webapp/WEB-INF/view/admin/dict/stopwords/admin_dict_stopwords_details.jsp

@@ -0,0 +1,123 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message
+		key="labels.dict_stopwords_configuration" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+</head>
+<body class="hold-transition skin-blue sidebar-mini">
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="dict" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.dict_stopwords_title" />
+				</h1>
+				<ol class="breadcrumb">
+					<li><la:link href="list">
+							<la:message key="labels.dict_list_link" />
+						</la:link></li>
+					<li><la:link href="list/0/?dictId=${f:u(dictId)}">
+							<la:message key="labels.dict_stopwords_list_link" />
+						</la:link></li>
+					<li class="active"><la:message
+							key="labels.dict_stopwords_link_details" /></li>
+				</ol>
+			</section>
+			<section class="content">
+				<la:form action="/admin/dict/stopwords/">
+					<la:hidden property="crudMode" />
+					<la:hidden property="dictId" />
+					<c:if test="${crudMode==2 || crudMode==3 || crudMode==4}">
+						<la:hidden property="id" />
+					</c:if>
+					<div class="row">
+						<div class="col-md-12">
+							<div
+								class="box <c:if test="${crudMode == 1}">box-success</c:if><c:if test="${crudMode == 2}">box-warning</c:if><c:if test="${crudMode == 3}">box-danger</c:if><c:if test="${crudMode == 4}">box-primary</c:if>">
+								<%-- Box Header --%>
+								<div class="box-header with-border">
+									<h3 class="box-title">
+										<c:if test="${crudMode == 1}">
+											<la:message key="labels.dict_stopwords_link_create" />
+										</c:if>
+										<c:if test="${crudMode == 2}">
+											<la:message key="labels.dict_stopwords_link_edit" />
+										</c:if>
+										<c:if test="${crudMode == 3}">
+											<la:message key="labels.dict_stopwords_link_delete" />
+										</c:if>
+										<c:if test="${crudMode == 4}">
+											<la:message key="labels.dict_stopwords_link_details" />
+										</c:if>
+									</h3>
+									<div class="btn-group pull-right">
+										<la:link href="/admin/dict"
+											styleClass="btn btn-default btn-xs">
+											<i class="fa fa-book"></i>
+											<la:message key="labels.dict_list_link" />
+										</la:link>
+										<la:link href="../list/1?dictId=${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-th-list"></i>
+											<la:message key="labels.dict_stopwords_list_link" />
+										</la:link>
+										<la:link href="../createnew/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-plus"></i>
+											<la:message key="labels.dict_stopwords_link_create" />
+										</la:link>
+										<la:link href="../downloadpage/${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-download"></i>
+											<la:message key="labels.dict_stopwords_link_download" />
+										</la:link>
+										<la:link href="../uploadpage/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-upload"></i>
+											<la:message key="labels.dict_stopwords_link_upload" />
+										</la:link>
+									</div>
+								</div>
+								<%-- Box Body --%>
+								<div class="box-body">
+									<%-- Message --%>
+									<div>
+										<la:info id="msg" message="true">
+											<div class="alert alert-info">${msg}</div>
+										</la:info>
+										<la:errors />
+									</div>
+									<%-- Form Fields --%>
+									<table class="table table-bordered">
+										<tbody>
+											<tr>
+												<th><la:message
+														key="labels.dict_stopwords_source" /></th>
+												<td>${f:h(input)}<la:hidden property="input" /></td>
+											</tr>
+										</tbody>
+									</table>
+								</div>
+								<!-- /.box-body -->
+								<div class="box-footer">
+									<jsp:include page="/WEB-INF/view/common/admin/crud/buttons.jsp"></jsp:include>
+								</div>
+								<!-- /.box-footer -->
+							</div>
+							<!-- /.box -->
+						</div>
+					</div>
+				</la:form>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+</body>
+</html>

+ 103 - 0
src/main/webapp/WEB-INF/view/admin/dict/stopwords/admin_dict_stopwords_download.jsp

@@ -0,0 +1,103 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message
+		key="labels.dict_stopwords_configuration" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+</head>
+<body class="hold-transition skin-blue sidebar-mini">
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="dict" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.dict_stopwords_title" />
+				</h1>
+				<ol class="breadcrumb">
+					<li><la:link href="list">
+							<la:message key="labels.dict_list_link" />
+						</la:link></li>
+					<li><la:link href="list/0/?dictId=${f:u(dictId)}">
+							<la:message key="labels.dict_stopwords_list_link" />
+						</la:link></li>
+					<li class="active"><la:message
+							key="labels.dict_stopwords_link_download" /></li>
+				</ol>
+			</section>
+			<section class="content">
+				<la:form action="/admin/dict/stopwords/">
+					<la:hidden property="dictId" />
+					<div class="row">
+						<div class="col-md-12">
+							<div class="box box-primary">
+								<div class="box-header with-border">
+									<h3 class="box-title">
+										<la:message key="labels.dict_stopwords_link_download" />
+									</h3>
+									<div class="btn-group pull-right">
+										<la:link href="/admin/dict"
+											styleClass="btn btn-default btn-xs">
+											<i class="fa fa-book"></i>
+											<la:message key="labels.dict_list_link" />
+										</la:link>
+										<la:link href="../list/0/?dictId=${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-th-list"></i>
+											<la:message key="labels.dict_stopwords_list_link" />
+										</la:link>
+										<la:link href="../createnew/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-plus"></i>
+											<la:message key="labels.dict_stopwords_link_create" />
+										</la:link>
+										<la:link href="../downloadpage/${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-download"></i>
+											<la:message key="labels.dict_stopwords_link_download" />
+										</la:link>
+										<la:link href="../uploadpage/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-upload"></i>
+											<la:message key="labels.dict_stopwords_link_upload" />
+										</la:link>
+									</div>
+								</div>
+								<!-- /.box-header -->
+								<div class="box-body">
+									<%-- Message --%>
+									<div>
+										<la:info id="msg" message="true">
+											<div class="alert alert-info">${msg}</div>
+										</la:info>
+										<la:errors />
+									</div>
+									<div class="form-group">
+										<label for="name" class="col-sm-12 control-label">${f:h(path)}</label>
+									</div>
+								</div>
+								<!-- /.box-body -->
+								<div class="box-footer">
+									<button type="submit" class="btn btn-primary" name="download"
+										value="<la:message key="labels.dict_stopwords_button_download" />">
+										<i class="fa fa-download"></i>
+										<la:message key="labels.dict_stopwords_button_download" />
+									</button>
+								</div>
+								<!-- /.box-footer -->
+							</div>
+							<!-- /.box -->
+						</div>
+					</div>
+				</la:form>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+</body>
+</html>

+ 120 - 0
src/main/webapp/WEB-INF/view/admin/dict/stopwords/admin_dict_stopwords_edit.jsp

@@ -0,0 +1,120 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message
+		key="labels.dict_stopwords_configuration" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+</head>
+<body class="hold-transition skin-blue sidebar-mini">
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="dict" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.dict_stopwords_title" />
+				</h1>
+				<ol class="breadcrumb">
+					<li><la:link href="list">
+							<la:message key="labels.dict_list_link" />
+						</la:link></li>
+					<li><la:link href="list/0/?dictId=${f:u(dictId)}">
+							<la:message key="labels.dict_stopwords_list_link" />
+						</la:link></li>
+					<c:if test="${crudMode == 1}">
+						<li class="active"><la:message
+								key="labels.dict_stopwords_link_create" /></li>
+					</c:if>
+					<c:if test="${crudMode == 2}">
+						<li class="active"><la:message
+								key="labels.dict_stopwords_link_edit" /></li>
+					</c:if>
+				</ol>
+			</section>
+			<section class="content">
+				<la:form action="/admin/dict/stopwords/" styleClass="form-horizontal">
+					<la:hidden property="crudMode" />
+					<la:hidden property="dictId" />
+					<c:if test="${crudMode==2}">
+						<la:hidden property="id" />
+					</c:if>
+					<div class="row">
+						<div class="col-md-12">
+							<div
+								class="box <c:if test="${crudMode == 1}">box-success</c:if><c:if test="${crudMode == 2}">box-warning</c:if>">
+								<div class="box-header with-border">
+									<h3 class="box-title">
+										<c:if test="${crudMode == 1}">
+											<la:message key="labels.dict_stopwords_link_create" />
+										</c:if>
+										<c:if test="${crudMode == 2}">
+											<la:message key="labels.dict_stopwords_link_edit" />
+										</c:if>
+									</h3>
+									<div class="btn-group pull-right">
+										<la:link href="/admin/dict"
+											styleClass="btn btn-default btn-xs">
+											<i class="fa fa-book"></i>
+											<la:message key="labels.dict_list_link" />
+										</la:link>
+										<la:link href="../list/1?dictId=${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-th-list"></i>
+											<la:message key="labels.dict_stopwords_list_link" />
+										</la:link>
+										<la:link href="../createnew/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-plus"></i>
+											<la:message key="labels.dict_stopwords_link_create" />
+										</la:link>
+										<la:link href="../downloadpage/${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-download"></i>
+											<la:message key="labels.dict_stopwords_link_download" />
+										</la:link>
+										<la:link href="../uploadpage/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-upload"></i>
+											<la:message key="labels.dict_stopwords_link_upload" />
+										</la:link>
+									</div>
+								</div>
+								<!-- /.box-header -->
+								<div class="box-body">
+									<div>
+										<la:info id="msg" message="true">
+											<div class="alert alert-info">${msg}</div>
+										</la:info>
+										<la:errors property="_global" />
+									</div>
+									<div class="form-group">
+										<label for="term" class="col-sm-3 control-label"><la:message
+												key="labels.dict_stopwords_source" /></label>
+										<div class="col-sm-9">
+											<la:errors property="input" />
+											<la:text styleId="input" property="input"
+												styleClass="form-control" />
+										</div>
+									</div>
+								</div>
+								<!-- /.box-body -->
+								<div class="box-footer">
+									<jsp:include page="/WEB-INF/view/common/admin/crud/buttons.jsp"></jsp:include>
+								</div>
+								<!-- /.box-footer -->
+							</div>
+							<!-- /.box -->
+						</div>
+					</div>
+				</la:form>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+</body>
+</html>

+ 107 - 0
src/main/webapp/WEB-INF/view/admin/dict/stopwords/admin_dict_stopwords_upload.jsp

@@ -0,0 +1,107 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message
+		key="labels.dict_stopwords_configuration" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+</head>
+<body class="hold-transition skin-blue sidebar-mini">
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="dict" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.dict_stopwords_title" />
+				</h1>
+				<ol class="breadcrumb">
+					<li><la:link href="list">
+							<la:message key="labels.dict_list_link" />
+						</la:link></li>
+					<li><la:link href="list/0/?dictId=${f:u(dictId)}">
+							<la:message key="labels.dict_stopwords_list_link" />
+						</la:link></li>
+					<li class="active"><la:message
+							key="labels.dict_stopwords_link_upload" /></li>
+				</ol>
+			</section>
+			<section class="content">
+				<la:form action="/admin/dict/stopwords/upload" enctype="multipart/form-data">
+					<la:hidden property="dictId" />
+					<div class="row">
+						<div class="col-md-12">
+							<div class="box box-primary">
+								<div class="box-header with-border">
+									<h3 class="box-title">
+										<la:message key="labels.dict_stopwords_link_upload" />
+									</h3>
+									<div class="btn-group pull-right">
+										<la:link href="/admin/dict"
+											styleClass="btn btn-default btn-xs">
+											<i class="fa fa-book"></i>
+											<la:message key="labels.dict_list_link" />
+										</la:link>
+										<la:link href="../list/0/?dictId=${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-th-list"></i>
+											<la:message key="labels.dict_stopwords_list_link" />
+										</la:link>
+										<la:link href="../createnew/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-plus"></i>
+											<la:message key="labels.dict_stopwords_link_create" />
+										</la:link>
+										<la:link href="../downloadpage/${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-download"></i>
+											<la:message key="labels.dict_stopwords_link_download" />
+										</la:link>
+										<la:link href="../uploadpage/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-upload"></i>
+											<la:message key="labels.dict_stopwords_link_upload" />
+										</la:link>
+									</div>
+								</div>
+								<!-- /.box-header -->
+								<div class="box-body">
+									<%-- Message --%>
+									<div>
+										<la:info id="msg" message="true">
+											<div class="alert alert-info">${msg}</div>
+										</la:info>
+										<la:errors />
+									</div>
+									<div class="form-group">
+										<label for="name" class="col-sm-3 control-label"><la:message
+												key="labels.dict_stopwords_file" /></label>
+										<div class="col-sm-9">
+											<input type="file" name="stopwordsFile" />
+										</div>
+									</div>
+								</div>
+								<!-- /.box-body -->
+								<div class="box-footer">
+									<button type="submit" class="btn btn-success"
+										value="<la:message key="labels.dict_stopwords_button_upload" />">
+										<i class="fa fa-upload"></i>
+										<la:message key="labels.dict_stopwords_button_upload" />
+									</button>
+								</div>
+								<!-- /.box-footer -->
+							</div>
+							<!-- /.box -->
+						</div>
+					</div>
+				</la:form>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+</body>
+</html>

+ 87 - 0
src/test/java/org/codelibs/fess/it/admin/dict/StopwordsTests.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012-2018 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.it.admin.dict;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+@Tag("it")
+public class StopwordsTests extends DictCrudTestBase {
+
+    private static final String NAME_PREFIX = "stopwordsTest_";
+    private static final String API_PATH = "/api/admin/dict/stopwords";
+    private static final String LIST_ENDPOINT_SUFFIX = "settings";
+    private static final String ITEM_ENDPOINT_SUFFIX = "setting";
+    private static final String DICT_TYPE = "stopwords";
+
+    private static final String KEY_PROPERTY = "input";
+
+    @Override
+    protected String getNamePrefix() {
+        return NAME_PREFIX;
+    }
+
+    @Override
+    protected String getApiPath() {
+        return API_PATH;
+    }
+
+    @Override
+    protected String getKeyProperty() {
+        return KEY_PROPERTY;
+    }
+
+    @Override
+    protected String getListEndpointSuffix() {
+        return LIST_ENDPOINT_SUFFIX + "/" + dictId;
+    }
+
+    @Override
+    protected String getItemEndpointSuffix() {
+        return ITEM_ENDPOINT_SUFFIX + "/" + dictId;
+    }
+
+    @Override
+    protected String getDictType() {
+        return DICT_TYPE;
+    }
+
+    @Override
+    protected Map<String, Object> createTestParam(int id) {
+        final Map<String, Object> requestBody = new HashMap<>();
+        final String keyProp = NAME_PREFIX + id;
+        requestBody.put(KEY_PROPERTY, keyProp);
+        return requestBody;
+    }
+
+    @Override
+    protected Map<String, Object> getUpdateMap() {
+        assertTrue(false); // Unreachable
+        return null;
+    }
+
+    @Test
+    void crudTest() {
+        testCreate();
+        testRead();
+        testDelete();
+    }
+}