Forráskód Böngészése

fix #1053 add /api/admin/searchlist

Shinsuke Sugaya 8 éve
szülő
commit
53fe2aeda2

+ 10 - 2
src/main/java/org/codelibs/fess/api/BaseJsonApiManager.java

@@ -138,8 +138,16 @@ public abstract class BaseJsonApiManager extends BaseApiManager {
                 buf.append(escapeJson(entry.getKey())).append(':').append(escapeJson(entry.getValue()));
             }
             buf.append('}');
-        } else if (obj instanceof Number) {
-            buf.append(obj);
+        } else if (obj instanceof Integer) {
+            buf.append(((Integer) obj).intValue());
+        } else if (obj instanceof Long) {
+            buf.append(((Long) obj).longValue());
+        } else if (obj instanceof Float) {
+            buf.append(((Float) obj).floatValue());
+        } else if (obj instanceof Double) {
+            buf.append(((Double) obj).doubleValue());
+        } else if (obj instanceof Boolean) {
+            buf.append(obj.toString());
         } else if (obj instanceof Date) {
             final SimpleDateFormat sdf = new SimpleDateFormat(CoreLibConstants.DATE_FORMAT_ISO_8601_EXTEND, Locale.ROOT);
             buf.append('\"').append(StringEscapeUtils.escapeJson(sdf.format(obj))).append('\"');

+ 27 - 0
src/main/java/org/codelibs/fess/api/json/JsonApiManager.java

@@ -147,6 +147,15 @@ public class JsonApiManager extends BaseJsonApiManager {
             final List<Map<String, Object>> documentItems = data.getDocumentItems();
             final FacetResponse facetResponse = data.getFacetResponse();
             final String queryId = data.getQueryId();
+            final String highlightParams = data.getAppendHighlightParams();
+            final boolean nextPage = data.isExistNextPage();
+            final boolean prevPage = data.isExistPrevPage();
+            final long startRecordNumber = data.getCurrentStartRecordNumber();
+            final long endRecordNumber = data.getCurrentEndRecordNumber();
+            final List<String> pageNumbers = data.getPageNumberList();
+            final boolean partial = data.isPartialResults();
+            final String searchQuery = data.getSearchQuery();
+            final long requestedTime = data.getRequestedTime();
 
             buf.append("\"q\":");
             buf.append(escapeJson(query));
@@ -168,6 +177,24 @@ public class JsonApiManager extends BaseJsonApiManager {
             buf.append(',');
             buf.append("\"page_count\":");
             buf.append(allPageCount);
+            buf.append(",\"highlight_params\":");
+            buf.append(escapeJson(highlightParams));
+            buf.append(",\"next_page\":");
+            buf.append(escapeJson(nextPage));
+            buf.append(",\"prev_page\":");
+            buf.append(escapeJson(prevPage));
+            buf.append(",\"start_record_number\":");
+            buf.append(startRecordNumber);
+            buf.append(",\"end_record_number\":");
+            buf.append(escapeJson(endRecordNumber));
+            buf.append(",\"page_numbers\":");
+            buf.append(escapeJson(pageNumbers));
+            buf.append(",\"partial\":");
+            buf.append(escapeJson(partial));
+            buf.append(",\"search_query\":");
+            buf.append(escapeJson(searchQuery));
+            buf.append(",\"requested_time\":");
+            buf.append(requestedTime);
             if (!documentItems.isEmpty()) {
                 buf.append(',');
                 buf.append("\"result\":[");

+ 43 - 33
src/main/java/org/codelibs/fess/app/web/admin/searchlist/AdminSearchlistAction.java

@@ -18,6 +18,7 @@ package org.codelibs.fess.app.web.admin.searchlist;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
@@ -32,6 +33,9 @@ import org.codelibs.fess.es.client.FessEsClient;
 import org.codelibs.fess.exception.InvalidQueryException;
 import org.codelibs.fess.exception.ResultOffsetExceededException;
 import org.codelibs.fess.helper.QueryHelper;
+import org.codelibs.fess.helper.SystemHelper;
+import org.codelibs.fess.mylasta.action.FessMessages;
+import org.codelibs.fess.mylasta.direction.FessConfig;
 import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.RenderDataUtil;
 import org.dbflute.optional.OptionalEntity;
@@ -41,6 +45,7 @@ import org.lastaflute.web.Execute;
 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.VaMessenger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -245,7 +250,7 @@ public class AdminSearchlistAction extends FessAdminAction {
     public HtmlResponse create(final CreateForm form) {
         verifyCrudMode(form.crudMode, CrudMode.CREATE);
         validate(form, messages -> {}, () -> asEditHtml());
-        validateCreateFields(form);
+        validateCreateFields(form, v -> throwValidationError(v, () -> asEditHtml()));
         verifyToken(() -> asEditHtml());
         getDoc(form).ifPresent(
                 entity -> {
@@ -274,7 +279,7 @@ public class AdminSearchlistAction extends FessAdminAction {
     public HtmlResponse update(final EditForm form) {
         verifyCrudMode(form.crudMode, CrudMode.EDIT);
         validate(form, messages -> {}, () -> asEditHtml());
-        validateUpdateFields(form);
+        validateUpdateFields(form, v -> throwValidationError(v, () -> asEditHtml()));
         logger.debug("DEBUUG:::role" + form.doc.get("role"));
         verifyToken(() -> asEditHtml());
         getDoc(form).ifPresent(
@@ -314,98 +319,100 @@ public class AdminSearchlistAction extends FessAdminAction {
     // ===================================================================================
     //                                                                       Validation
     //                                                                           =========
-    protected void validateCreateFields(final CreateForm form) {
+    public static void validateCreateFields(final CreateForm form, final Consumer<VaMessenger<FessMessages>> throwError) {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
 
         if (!fessConfig.validateIndexRequiredFields(form.doc)) {
             final List<String> invalidRequiredFields = fessConfig.invalidIndexRequiredFields(form.doc);
-            throwValidationError(messages -> {
+            throwError.accept(messages -> {
                 messages.addErrorsCrudFailedToCreateInstance("doc." + invalidRequiredFields.get(0));
                 // TODO messages.addConstraintsRequiredMessage("doc." + invalidRequiredFields.get(0), invalidRequiredFields.get(0));
-                }, () -> asEditHtml());
+                });
         }
 
         if (!fessConfig.validateIndexArrayFields(form.doc)) {
             final List<String> invalidArrayFields = fessConfig.invalidIndexArrayFields(form.doc);
-            throwValidationError(messages -> {
+            throwError.accept(messages -> {
                 messages.addErrorsCrudFailedToCreateInstance("doc." + invalidArrayFields.get(0));
-            }, () -> asEditHtml());
+            });
         }
         if (!fessConfig.validateIndexDateFields(form.doc)) {
             final List<String> invalidDateFields = fessConfig.invalidIndexDateFields(form.doc);
-            throwValidationError(messages -> {
+            throwError.accept(messages -> {
                 messages.addErrorsCrudFailedToCreateInstance("doc." + invalidDateFields.get(0));
-            }, () -> asEditHtml());
+            });
         }
         if (!fessConfig.validateIndexIntegerFields(form.doc)) {
             final List<String> invalidIntegerFields = fessConfig.invalidIndexIntegerFields(form.doc);
-            throwValidationError(messages -> {
+            throwError.accept(messages -> {
                 messages.addErrorsCrudFailedToCreateInstance("doc." + invalidIntegerFields.get(0));
-            }, () -> asEditHtml());
+            });
         }
         if (!fessConfig.validateIndexLongFields(form.doc)) {
             final List<String> invalidLongFields = fessConfig.invalidIndexLongFields(form.doc);
-            throwValidationError(messages -> {
+            throwError.accept(messages -> {
                 messages.addErrorsCrudFailedToCreateInstance("doc." + invalidLongFields.get(0));
-            }, () -> asEditHtml());
+            });
         }
         if (!fessConfig.validateIndexFloatFields(form.doc)) {
             final List<String> invalidFloatFields = fessConfig.invalidIndexFloatFields(form.doc);
-            throwValidationError(messages -> {
+            throwError.accept(messages -> {
                 messages.addErrorsCrudFailedToCreateInstance("doc." + invalidFloatFields.get(0));
-            }, () -> asEditHtml());
+            });
         }
         if (!fessConfig.validateIndexDoubleFields(form.doc)) {
             final List<String> invalidDoubleFields = fessConfig.invalidIndexDoubleFields(form.doc);
-            throwValidationError(messages -> {
+            throwError.accept(messages -> {
                 messages.addErrorsCrudFailedToCreateInstance("doc." + invalidDoubleFields.get(0));
-            }, () -> asEditHtml());
+            });
         }
     }
 
-    protected void validateUpdateFields(final EditForm form) {
+    public static void validateUpdateFields(final EditForm form, final Consumer<VaMessenger<FessMessages>> throwError) {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
 
         if (!fessConfig.validateIndexRequiredFields(form.doc)) {
             final List<String> invalidRequiredFields = fessConfig.invalidIndexRequiredFields(form.doc);
-            throwValidationError(messages -> {
+            throwError.accept(messages -> {
                 messages.addErrorsCrudCouldNotFindCrudTable("doc." + invalidRequiredFields.get(0), form.docId);
-            }, () -> asEditHtml());
+            });
         }
 
         if (!fessConfig.validateIndexArrayFields(form.doc)) {
             final List<String> invalidArrayFields = fessConfig.invalidIndexArrayFields(form.doc);
-            throwValidationError(messages -> {
+            throwError.accept(messages -> {
                 messages.addErrorsCrudCouldNotFindCrudTable("doc." + invalidArrayFields.get(0), form.docId);
-            }, () -> asEditHtml());
+            });
         }
         if (!fessConfig.validateIndexDateFields(form.doc)) {
             final List<String> invalidDateFields = fessConfig.invalidIndexDateFields(form.doc);
-            throwValidationError(messages -> {
+            throwError.accept(messages -> {
                 messages.addErrorsCrudCouldNotFindCrudTable("doc." + invalidDateFields.get(0), form.docId);
-            }, () -> asEditHtml());
+            });
         }
         if (!fessConfig.validateIndexIntegerFields(form.doc)) {
             final List<String> invalidIntegerFields = fessConfig.invalidIndexIntegerFields(form.doc);
-            throwValidationError(messages -> {
+            throwError.accept(messages -> {
                 messages.addErrorsCrudCouldNotFindCrudTable("doc." + invalidIntegerFields.get(0), form.docId);
-            }, () -> asEditHtml());
+            });
         }
         if (!fessConfig.validateIndexLongFields(form.doc)) {
             final List<String> invalidLongFields = fessConfig.invalidIndexLongFields(form.doc);
-            throwValidationError(messages -> {
+            throwError.accept(messages -> {
                 messages.addErrorsCrudCouldNotFindCrudTable("doc." + invalidLongFields.get(0), form.docId);
-            }, () -> asEditHtml());
+            });
         }
         if (!fessConfig.validateIndexFloatFields(form.doc)) {
             final List<String> invalidFloatFields = fessConfig.invalidIndexFloatFields(form.doc);
-            throwValidationError(messages -> {
+            throwError.accept(messages -> {
                 messages.addErrorsCrudCouldNotFindCrudTable("doc." + invalidFloatFields.get(0), form.docId);
-            }, () -> asEditHtml());
+            });
         }
         if (!fessConfig.validateIndexDoubleFields(form.doc)) {
             final List<String> invalidDoubleFields = fessConfig.invalidIndexDoubleFields(form.doc);
-            throwValidationError(messages -> {
+            throwError.accept(messages -> {
                 messages.addErrorsCrudCouldNotFindCrudTable("doc." + invalidDoubleFields.get(0), form.docId);
-            }, () -> asEditHtml());
+            });
         }
     }
 
@@ -420,7 +427,10 @@ public class AdminSearchlistAction extends FessAdminAction {
         }
     }
 
-    protected OptionalEntity<Map<String, Object>> getDoc(final CreateForm form) {
+    public static OptionalEntity<Map<String, Object>> getDoc(final CreateForm form) {
+        final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        final FessEsClient fessEsClient = ComponentUtil.getFessEsClient();
         switch (form.crudMode) {
         case CrudMode.CREATE:
             final Map<String, Object> entity = new HashMap<>();

+ 106 - 0
src/main/java/org/codelibs/fess/app/web/api/ApiResult.java

@@ -15,14 +15,17 @@
  */
 package org.codelibs.fess.app.web.api;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.stream.Collectors;
 
 import org.codelibs.fess.Constants;
+import org.codelibs.fess.entity.SearchRenderData;
 import org.codelibs.fess.mylasta.action.FessMessages;
 import org.codelibs.fess.util.ComponentUtil;
+import org.codelibs.fess.util.FacetResponse;
 import org.lastaflute.web.util.LaRequestUtil;
 import org.lastaflute.web.validation.VaMessenger;
 
@@ -81,6 +84,20 @@ public class ApiResult {
         }
     }
 
+    public static class ApiDeleteResponse extends ApiResponse {
+        protected long count = 1;
+
+        public ApiDeleteResponse count(final long count) {
+            this.count = count;
+            return this;
+        }
+
+        @Override
+        public ApiResult result() {
+            return new ApiResult(this);
+        }
+    }
+
     public static class ApiConfigResponse extends ApiResponse {
         protected Object setting;
 
@@ -115,6 +132,95 @@ public class ApiResult {
         }
     }
 
+    public static class ApiDocResponse extends ApiResponse {
+        protected Object doc;
+
+        public ApiDocResponse doc(final Object doc) {
+            this.doc = doc;
+            return this;
+        }
+
+        @Override
+        public ApiResult result() {
+            return new ApiResult(this);
+        }
+    }
+
+    public static class ApiDocsResponse extends ApiResponse {
+        protected String queryId;
+        protected List<Map<String, Object>> result;
+        protected String highlightParams;
+        protected String execTime;
+        protected int pageSize;
+        protected int pageNumber;
+        protected long recordCount;
+        protected int pageCount;
+        protected boolean nextPage;
+        protected boolean prevPage;
+        protected long startRecordNumber;
+        protected long endRecordNumber;
+        protected List<String> pageNumbers;
+        protected boolean partial;
+        protected long queryTime;
+        protected String searchQuery;
+        protected long requestedTime;
+        protected List<Map<String, Object>> facetField;
+        protected List<Map<String, Object>> facetQuery;
+
+        public ApiDocsResponse renderData(final SearchRenderData data) {
+            queryId = data.getQueryId();
+            result = data.getDocumentItems();
+            highlightParams = data.getAppendHighlightParams();
+            execTime = data.getExecTime();
+            pageSize = data.getPageSize();
+            pageNumber = data.getCurrentPageNumber();
+            recordCount = data.getAllRecordCount();
+            pageCount = data.getAllPageCount();
+            nextPage = data.isExistNextPage();
+            prevPage = data.isExistPrevPage();
+            startRecordNumber = data.getCurrentStartRecordNumber();
+            endRecordNumber = data.getCurrentEndRecordNumber();
+            pageNumbers = data.getPageNumberList();
+            partial = data.isPartialResults();
+            queryTime = data.getQueryTime();
+            searchQuery = data.getSearchQuery();
+            requestedTime = data.getRequestedTime();
+            final FacetResponse facetResponse = data.getFacetResponse();
+            if (facetResponse != null && facetResponse.hasFacetResponse()) {
+                // facet field
+                if (facetResponse.getFieldList() != null) {
+                    facetField = facetResponse.getFieldList().stream().map(field -> {
+                        Map<String, Object> fieldMap = new HashMap<>(2, 1f);
+                        fieldMap.put("name", field.getName());
+                        fieldMap.put("result", field.getValueCountMap().entrySet().stream().map(e -> {
+                            Map<String, Object> valueCount = new HashMap<>(2, 1f);
+                            valueCount.put("value", e.getKey());
+                            valueCount.put("count", e.getValue());
+                            return valueCount;
+                        }).collect(Collectors.toList()));
+                        return fieldMap;
+                    }).collect(Collectors.toList());
+                }
+                // facet q
+                if (facetResponse.getQueryCountMap() != null) {
+                    facetQuery = facetResponse.getQueryCountMap().entrySet().stream().map(e -> {
+                        Map<String, Object> valueCount = new HashMap<>(2, 1f);
+                        valueCount.put("value", e.getKey());
+                        valueCount.put("count", e.getValue());
+                        return valueCount;
+                    }).collect(Collectors.toList());
+
+                }
+            }
+            return this;
+        }
+
+        @Override
+        public ApiResult result() {
+            return new ApiResult(this);
+        }
+    }
+
     public static class ApiLogResponse extends ApiResponse {
         protected Object log;
 

+ 227 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/searchlist/ApiAdminSearchlistAction.java

@@ -0,0 +1,227 @@
+/*
+ * Copyright 2012-2017 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.searchlist;
+
+import static org.codelibs.fess.app.web.admin.searchlist.AdminSearchlistAction.getDoc;
+import static org.codelibs.fess.app.web.admin.searchlist.AdminSearchlistAction.validateCreateFields;
+import static org.codelibs.fess.app.web.admin.searchlist.AdminSearchlistAction.validateUpdateFields;
+
+import java.util.Map;
+
+import javax.annotation.Resource;
+
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.app.service.SearchService;
+import org.codelibs.fess.app.web.CrudMode;
+import org.codelibs.fess.app.web.api.ApiResult;
+import org.codelibs.fess.app.web.api.ApiResult.ApiDeleteResponse;
+import org.codelibs.fess.app.web.api.ApiResult.ApiDocResponse;
+import org.codelibs.fess.app.web.api.ApiResult.ApiDocsResponse;
+import org.codelibs.fess.app.web.api.ApiResult.ApiResponse;
+import org.codelibs.fess.app.web.api.ApiResult.ApiUpdateResponse;
+import org.codelibs.fess.app.web.api.ApiResult.Status;
+import org.codelibs.fess.app.web.api.admin.FessApiAdminAction;
+import org.codelibs.fess.entity.SearchRenderData;
+import org.codelibs.fess.es.client.FessEsClient;
+import org.codelibs.fess.exception.InvalidQueryException;
+import org.codelibs.fess.exception.ResultOffsetExceededException;
+import org.codelibs.fess.util.ComponentUtil;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.lastaflute.web.Execute;
+import org.lastaflute.web.response.JsonResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author shinsuke
+ */
+public class ApiAdminSearchlistAction extends FessApiAdminAction {
+
+    // ===================================================================================
+    // Constant
+    //
+    private static final Logger logger = LoggerFactory.getLogger(ApiAdminSearchlistAction.class);
+
+    // ===================================================================================
+    // Attribute
+    // =========
+    @Resource
+    protected SearchService searchService;
+
+    @Resource
+    protected FessEsClient fessEsClient;
+
+    // ===================================================================================
+    //                                                                      Search Execute
+    //                                                                      ==============
+
+    // GET /api/admin/searchlist
+    // POST /api/admin/searchlist
+    @Execute
+    public JsonResponse<ApiResult> docs(final SearchBody body) {
+        validateApi(body, messages -> {});
+
+        if (StringUtil.isBlank(body.q)) {
+            // query matches on all documents.
+            body.q = Constants.MATCHES_ALL_QUERY;
+        }
+        final SearchRenderData renderData = new SearchRenderData();
+        body.initialize();
+        try {
+            searchService.search(body, renderData, getUserBean());
+            return asJson(new ApiDocsResponse().renderData(renderData).status(Status.OK).result());
+        } catch (final InvalidQueryException e) {
+            if (logger.isDebugEnabled()) {
+                logger.debug(e.getMessage(), e);
+            }
+            throwValidationErrorApi(e.getMessageCode());
+        } catch (final ResultOffsetExceededException e) {
+            if (logger.isDebugEnabled()) {
+                logger.debug(e.getMessage(), e);
+            }
+            throwValidationErrorApi(messages -> messages.addErrorsResultSizeExceeded(GLOBAL));
+        }
+
+        throwValidationErrorApi(messages -> messages.addErrorsInvalidQueryUnknown(GLOBAL));
+        return null; // ignore
+    }
+
+    // GET /api/admin/searchlist/doc/{id}
+    @Execute
+    public JsonResponse<ApiResult> get$doc(final String id) {
+        return asJson(new ApiDocResponse()
+                .doc(fessEsClient.getDocument(fessConfig.getIndexDocumentUpdateIndex(), fessConfig.getIndexDocumentType(), builder -> {
+                    builder.setQuery(QueryBuilders.termQuery(fessConfig.getIndexFieldDocId(), id));
+                    return true;
+                }).orElseGet(() -> {
+                    throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id));
+                    return null;
+                })).status(Status.OK).result());
+    }
+
+    // PUT /api/admin/searchlist/doc
+    @Execute
+    public JsonResponse<ApiResult> put$doc(final CreateBody body) {
+        validateApi(body, messages -> {});
+        validateCreateFields(body, v -> throwValidationErrorApi(v));
+        body.crudMode = CrudMode.CREATE;
+        final Map<String, Object> doc = getDoc(body).map(entity -> {
+            try {
+                entity.putAll(fessConfig.convertToStorableDoc(body.doc));
+
+                final String newId = ComponentUtil.getCrawlingInfoHelper().generateId(entity);
+                entity.put(fessConfig.getIndexFieldId(), newId);
+
+                final String index = fessConfig.getIndexDocumentUpdateIndex();
+                final String type = fessConfig.getIndexDocumentType();
+                fessEsClient.store(index, type, entity);
+                saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL));
+            } catch (final Exception e) {
+                logger.error("Failed to add " + entity, e);
+                throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateCrudTable(GLOBAL, buildThrowableMessage(e)));
+            }
+            return entity;
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL));
+            return null;
+        });
+        return asJson(new ApiUpdateResponse().id(doc.get(fessConfig.getIndexFieldDocId()).toString()).created(true).status(Status.OK)
+                .result());
+    }
+
+    // POST /api/admin/searchlist/doc
+    @Execute
+    public JsonResponse<ApiResult> post$doc(final EditBody body) {
+        validateApi(body, messages -> {});
+        validateUpdateFields(body, v -> throwValidationErrorApi(v));
+        body.crudMode = CrudMode.EDIT;
+        final Map<String, Object> doc = getDoc(body).map(entity -> {
+            try {
+                entity.putAll(fessConfig.convertToStorableDoc(body.doc));
+
+                final String newId = ComponentUtil.getCrawlingInfoHelper().generateId(entity);
+                String oldId = null;
+                if (newId.equals(body.id)) {
+                    entity.put(fessConfig.getIndexFieldId(), body.id);
+                    entity.put(fessConfig.getIndexFieldVersion(), body.version);
+                } else {
+                    oldId = body.id;
+                    entity.put(fessConfig.getIndexFieldId(), newId);
+                    entity.remove(fessConfig.getIndexFieldVersion());
+                }
+
+                final String index = fessConfig.getIndexDocumentUpdateIndex();
+                final String type = fessConfig.getIndexDocumentType();
+                fessEsClient.store(index, type, entity);
+                if (oldId != null) {
+                    fessEsClient.delete(index, type, oldId, body.version);
+                }
+                saveInfo(messages -> messages.addSuccessCrudUpdateCrudTable(GLOBAL));
+            } catch (final Exception e) {
+                logger.error("Failed to update " + entity, e);
+                throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToUpdateCrudTable(GLOBAL, buildThrowableMessage(e)));
+            }
+            return entity;
+        }).orElseGet(() -> {
+            throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, body.id));
+            return null;
+        });
+        return asJson(new ApiUpdateResponse().id(doc.get(fessConfig.getIndexFieldDocId()).toString()).created(false).status(Status.OK)
+                .result());
+    }
+
+    // DELETE /api/admin/searchlist/doc/{id}
+    @Execute
+    public JsonResponse<ApiResult> delete$doc(final String id) {
+        try {
+            final QueryBuilder query = QueryBuilders.termQuery(fessConfig.getIndexFieldDocId(), id);
+            fessEsClient.deleteByQuery(fessConfig.getIndexDocumentUpdateIndex(), fessConfig.getIndexDocumentType(), query);
+            saveInfo(messages -> messages.addSuccessDeleteDocFromIndex(GLOBAL));
+        } catch (final Exception e) {
+            throwValidationErrorApi(messages -> messages.addErrorsFailedToDeleteDocInAdmin(GLOBAL));
+        }
+        return asJson(new ApiResponse().status(Status.OK).result());
+    }
+
+    // DELETE /api/admin/searchlist/query
+    @Execute
+    public JsonResponse<ApiResult> delete$query(final SearchBody body) {
+        validateApi(body, messages -> {});
+
+        if (StringUtil.isBlank(body.q)) {
+            throwValidationErrorApi(messages -> messages.addErrorsInvalidQueryUnknown(GLOBAL));
+        }
+        try {
+            final int count = searchService.deleteByQuery(request, body);
+            return asJson(new ApiDeleteResponse().count(count).status(Status.OK).result());
+        } catch (final InvalidQueryException e) {
+            if (logger.isDebugEnabled()) {
+                logger.debug(e.getMessage(), e);
+            }
+            throwValidationErrorApi(e.getMessageCode());
+        } catch (final ResultOffsetExceededException e) {
+            if (logger.isDebugEnabled()) {
+                logger.debug(e.getMessage(), e);
+            }
+            throwValidationErrorApi(messages -> messages.addErrorsResultSizeExceeded(GLOBAL));
+        }
+
+        throwValidationErrorApi(messages -> messages.addErrorsInvalidQueryUnknown(GLOBAL));
+        return null; // ignore
+    }
+}

+ 22 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/searchlist/CreateBody.java

@@ -0,0 +1,22 @@
+/*
+ * Copyright 2012-2017 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.searchlist;
+
+import org.codelibs.fess.app.web.admin.searchlist.CreateForm;
+
+public class CreateBody extends CreateForm {
+
+}

+ 22 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/searchlist/EditBody.java

@@ -0,0 +1,22 @@
+/*
+ * Copyright 2012-2017 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.searchlist;
+
+import org.codelibs.fess.app.web.admin.searchlist.EditForm;
+
+public class EditBody extends EditForm {
+
+}

+ 22 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/searchlist/SearchBody.java

@@ -0,0 +1,22 @@
+/*
+ * Copyright 2012-2017 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.searchlist;
+
+import org.codelibs.fess.app.web.admin.searchlist.ListForm;
+
+public class SearchBody extends ListForm {
+
+}