View Javadoc
1   /*
2    * Copyright 2012-2025 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.codelibs.fess.app.web.admin.sereq;
17  
18  import java.io.BufferedReader;
19  import java.io.File;
20  import java.io.FileInputStream;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.util.Locale;
24  
25  import org.apache.logging.log4j.LogManager;
26  import org.apache.logging.log4j.Logger;
27  import org.codelibs.core.io.CopyUtil;
28  import org.codelibs.core.io.ReaderUtil;
29  import org.codelibs.core.lang.StringUtil;
30  import org.codelibs.curl.CurlRequest;
31  import org.codelibs.curl.CurlResponse;
32  import org.codelibs.fess.Constants;
33  import org.codelibs.fess.annotation.Secured;
34  import org.codelibs.fess.app.web.base.FessAdminAction;
35  import org.codelibs.fess.helper.CurlHelper;
36  import org.codelibs.fess.util.ComponentUtil;
37  import org.lastaflute.web.Execute;
38  import org.lastaflute.web.response.ActionResponse;
39  import org.lastaflute.web.response.HtmlResponse;
40  import org.lastaflute.web.ruts.process.ActionRuntime;
41  
42  /**
43   * Admin action for Search Request.
44   *
45   */
46  public class AdminSereqAction extends FessAdminAction {
47  
48      /**
49       * Default constructor.
50       */
51      public AdminSereqAction() {
52          super();
53      }
54  
55      /** Role name for admin search request operations */
56      public static final String ROLE = "admin-sereq";
57  
58      private static final Logger logger = LogManager.getLogger(AdminSereqAction.class);
59  
60      @Override
61      protected void setupHtmlData(final ActionRuntime runtime) {
62          super.setupHtmlData(runtime);
63          runtime.registerData("helpLink", systemHelper.getHelpLink(fessConfig.getOnlineHelpNameSereq()));
64      }
65  
66      @Override
67      protected String getActionRole() {
68          return ROLE;
69      }
70  
71      /**
72       * Displays the search request management index page.
73       *
74       * @return HTML response for the search request page
75       */
76      @Execute
77      @Secured({ ROLE, ROLE + VIEW })
78      public HtmlResponse index() {
79          return asListHtml(this::saveToken);
80      }
81  
82      /**
83       * Processes uploaded search request files and executes them against the search engine.
84       *
85       * @param form the upload form containing the request file
86       * @return action response with the search results or error page
87       */
88      @Execute
89      @Secured({ ROLE })
90      public ActionResponse upload(final UploadForm form) {
91          validate(form, messages -> {}, () -> asListHtml(null));
92          verifyTokenKeep(() -> asListHtml(this::saveToken));
93  
94          String header = null;
95          final StringBuilder buf = new StringBuilder(1000);
96          try (final BufferedReader reader = new BufferedReader(new InputStreamReader(form.requestFile.getInputStream(), Constants.UTF_8))) {
97              header = ReaderUtil.readLine(reader);
98              if (header == null) {
99                  throwValidationError(messages -> messages.addErrorsInvalidHeaderForRequestFile(GLOBAL, "no header"),
100                         () -> asListHtml(this::saveToken));
101                 return redirect(getClass()); // no-op
102             }
103             String line;
104             while ((line = ReaderUtil.readLine(reader)) != null) {
105                 buf.append(line);
106             }
107         } catch (final Exception e) {
108             throwValidationError(messages -> messages.addErrorsFailedToReadRequestFile(GLOBAL, e.getMessage()),
109                     () -> asListHtml(this::saveToken));
110         }
111 
112         final CurlRequest curlRequest = getCurlRequest(header);
113         if (curlRequest == null) {
114             final String msg = header;
115             throwValidationError(messages -> messages.addErrorsInvalidHeaderForRequestFile(GLOBAL, msg), () -> asListHtml(this::saveToken));
116         } else {
117             try (final CurlResponse response = curlRequest.body(buf.toString()).execute()) {
118                 final File tempFile = ComponentUtil.getSystemHelper().createTempFile("sereq_", ".json");
119                 try (final InputStream in = response.getContentAsStream()) {
120                     CopyUtil.copy(in, tempFile);
121                 } catch (final Exception e1) {
122                     if (tempFile != null && tempFile.exists() && !tempFile.delete()) {
123                         logger.warn("Failed to delete {}", tempFile.getAbsolutePath());
124                     }
125                     throw e1;
126                 }
127                 return asStream("es_" + ComponentUtil.getSystemHelper().getCurrentTimeAsLong() + ".json").contentTypeOctetStream()
128                         .stream(out -> {
129                             try (final InputStream in = new FileInputStream(tempFile)) {
130                                 out.write(in);
131                             } finally {
132                                 if (tempFile.exists() && !tempFile.delete()) {
133                                     logger.warn("Failed to delete {}", tempFile.getAbsolutePath());
134                                 }
135                             }
136                         });
137             } catch (final Exception e) {
138                 logger.warn("Failed to process request file: {}", form.requestFile.getFileName(), e);
139                 throwValidationError(messages -> messages.addErrorsInvalidHeaderForRequestFile(GLOBAL, e.getMessage()),
140                         () -> asListHtml(this::saveToken));
141             }
142         }
143         return redirect(getClass()); // no-op
144     }
145 
146     /**
147      * Creates a CURL request from the provided header string.
148      *
149      * @param header the header string containing HTTP method and path
150      * @return CURL request object or null if header is invalid
151      */
152     private CurlRequest getCurlRequest(final String header) {
153         if (StringUtil.isBlank(header)) {
154             return null;
155         }
156 
157         final String[] values = header.split(" ");
158         if (values.length != 2) {
159             return null;
160         }
161 
162         final String path;
163         if (values[1].startsWith("/")) {
164             path = values[1];
165         } else {
166             path = "/" + values[1];
167         }
168 
169         final CurlHelper curlHelper = ComponentUtil.getCurlHelper();
170         switch (values[0].toUpperCase(Locale.ROOT)) {
171         case "GET":
172             return curlHelper.get(path);
173         case "POST":
174             return curlHelper.post(path);
175         case "PUT":
176             return curlHelper.put(path);
177         case "DELETE":
178             return curlHelper.delete(path);
179         default:
180             break;
181         }
182         return null;
183     }
184 
185     /**
186      * Creates an HTML response for the list page with optional pre-processing.
187      *
188      * @param runnable optional runnable to execute before rendering (can be null)
189      * @return HTML response for the search request list page
190      */
191     private HtmlResponse asListHtml(final Runnable runnable) {
192         if (runnable != null) {
193             runnable.run();
194         }
195         return asHtml(path_AdminSereq_AdminSereqJsp).useForm(UploadForm.class);
196     }
197 
198 }