View Javadoc
1   /*
2    * Copyright 2012-2021 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.dict.mapping;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import javax.annotation.Resource;
25  
26  import org.codelibs.core.beans.util.BeanUtil;
27  import org.codelibs.core.lang.StringUtil;
28  import org.codelibs.fess.Constants;
29  import org.codelibs.fess.annotation.Secured;
30  import org.codelibs.fess.app.pager.CharMappingPager;
31  import org.codelibs.fess.app.service.CharMappingService;
32  import org.codelibs.fess.app.web.CrudMode;
33  import org.codelibs.fess.app.web.admin.dict.AdminDictAction;
34  import org.codelibs.fess.app.web.base.FessAdminAction;
35  import org.codelibs.fess.app.web.base.FessBaseAction;
36  import org.codelibs.fess.dict.mapping.CharMappingItem;
37  import org.codelibs.fess.util.ComponentUtil;
38  import org.codelibs.fess.util.RenderDataUtil;
39  import org.dbflute.optional.OptionalEntity;
40  import org.dbflute.optional.OptionalThing;
41  import org.lastaflute.web.Execute;
42  import org.lastaflute.web.response.ActionResponse;
43  import org.lastaflute.web.response.HtmlResponse;
44  import org.lastaflute.web.response.render.RenderData;
45  import org.lastaflute.web.ruts.process.ActionRuntime;
46  import org.lastaflute.web.validation.VaErrorHook;
47  import org.lastaflute.web.validation.exception.ValidationErrorException;
48  
49  /**
50   * @author nullpos
51   * @author ma2tani
52   */
53  public class AdminDictMappingAction extends FessAdminAction {
54  
55      public static final String ROLE = "admin-dict";
56  
57      // ===================================================================================
58      //                                                                           Attribute
59      //                                                                           =========
60      @Resource
61      private CharMappingService charMappingService;
62      @Resource
63      private CharMappingPager charMappingPager;
64  
65      // ===================================================================================
66      //                                                                               Hook
67      //                                                                              ======
68      @Override
69      protected void setupHtmlData(final ActionRuntime runtime) {
70          super.setupHtmlData(runtime);
71          runtime.registerData("helpLink", systemHelper.getHelpLink(fessConfig.getOnlineHelpNameDictMapping()));
72      }
73  
74      @Override
75      protected String getActionRole() {
76          return ROLE;
77      }
78  
79      // ===================================================================================
80      //                                                                      Search Execute
81      //                                                                      ==============
82      @Execute
83      @Secured({ ROLE, ROLE + VIEW })
84      public HtmlResponse index(final SearchForm form) {
85          validate(form, messages -> {}, this::asDictIndexHtml);
86          charMappingPager.clear();
87          return asHtml(path_AdminDictMapping_AdminDictMappingJsp).renderWith(data -> {
88              searchPaging(data, form);
89          });
90      }
91  
92      @Execute
93      @Secured({ ROLE, ROLE + VIEW })
94      public HtmlResponse list(final OptionalThing<Integer> pageNumber, final SearchForm form) {
95          validate(form, messages -> {}, this::asDictIndexHtml);
96          pageNumber.ifPresent(num -> {
97              charMappingPager.setCurrentPageNumber(pageNumber.get());
98          }).orElse(() -> {
99              charMappingPager.setCurrentPageNumber(0);
100         });
101         return asHtml(path_AdminDictMapping_AdminDictMappingJsp).renderWith(data -> {
102             searchPaging(data, form);
103         });
104     }
105 
106     @Execute
107     @Secured({ ROLE, ROLE + VIEW })
108     public HtmlResponse search(final SearchForm form) {
109         validate(form, messages -> {}, this::asDictIndexHtml);
110         copyBeanToBean(form, charMappingPager, op -> op.exclude(Constants.PAGER_CONVERSION_RULE));
111         return asHtml(path_AdminDictMapping_AdminDictMappingJsp).renderWith(data -> {
112             searchPaging(data, form);
113         });
114     }
115 
116     @Execute
117     @Secured({ ROLE, ROLE + VIEW })
118     public HtmlResponse reset(final SearchForm form) {
119         validate(form, messages -> {}, this::asDictIndexHtml);
120         charMappingPager.clear();
121         return asHtml(path_AdminDictMapping_AdminDictMappingJsp).renderWith(data -> {
122             searchPaging(data, form);
123         });
124     }
125 
126     protected void searchPaging(final RenderData data, final SearchForm form) {
127         // page navi
128         RenderDataUtil.register(data, "charMappingItemItems", charMappingService.getCharMappingList(form.dictId, charMappingPager));
129 
130         // restore from pager
131         BeanUtil.copyBeanToBean(charMappingPager, form, op -> {
132             op.exclude(Constants.PAGER_CONVERSION_RULE);
133         });
134     }
135 
136     // ===================================================================================
137     //                                                                        Edit Execute
138     //                                                                        ============
139     // -----------------------------------------------------
140     //                                            Entry Page
141     //                                            ----------
142     @Execute
143     @Secured({ ROLE })
144     public HtmlResponse createnew(final String dictId) {
145         saveToken();
146         return asHtml(path_AdminDictMapping_AdminDictMappingEditJsp).useForm(CreateForm.class, op -> {
147             op.setup(form -> {
148                 form.initialize();
149                 form.crudMode = CrudMode.CREATE;
150                 form.dictId = dictId;
151             });
152         });
153     }
154 
155     @Execute
156     @Secured({ ROLE })
157     public HtmlResponse edit(final EditForm form) {
158         validate(form, messages -> {}, () -> asListHtml(form.dictId));
159         charMappingService.getCharMappingItem(form.dictId, form.id).ifPresent(entity -> {
160             form.inputs = entity.getInputsValue();
161             form.output = entity.getOutput();
162         }).orElse(() -> {
163             throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.getDisplayId()),
164                     () -> asListHtml(form.dictId));
165         });
166         saveToken();
167         if (form.crudMode.intValue() == CrudMode.EDIT) {
168             // back
169             form.crudMode = CrudMode.DETAILS;
170             return asDetailsHtml();
171         }
172         form.crudMode = CrudMode.EDIT;
173         return asEditHtml();
174     }
175 
176     // -----------------------------------------------------
177     //                                               Details
178     //                                               -------
179     @Execute
180     @Secured({ ROLE, ROLE + VIEW })
181     public HtmlResponse details(final String dictId, final int crudMode, final long id) {
182         verifyCrudMode(crudMode, CrudMode.DETAILS, dictId);
183         saveToken();
184         return asDetailsHtml().useForm(EditForm.class, op -> {
185             op.setup(form -> {
186                 charMappingService.getCharMappingItem(dictId, id).ifPresent(entity -> {
187                     form.inputs = entity.getInputsValue();
188                     form.output = entity.getOutput();
189                 }).orElse(() -> {
190                     throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, dictId + ":" + id),
191                             () -> asListHtml(dictId));
192                 });
193                 form.id = id;
194                 form.crudMode = crudMode;
195                 form.dictId = dictId;
196             });
197         });
198     }
199 
200     // -----------------------------------------------------
201     //                                              Download
202     //                                               -------
203     @Execute
204     @Secured({ ROLE, ROLE + VIEW })
205     public HtmlResponse downloadpage(final String dictId) {
206         saveToken();
207         return asHtml(path_AdminDictMapping_AdminDictMappingDownloadJsp).useForm(DownloadForm.class, op -> {
208             op.setup(form -> {
209                 form.dictId = dictId;
210             });
211         }).renderWith(data -> {
212             charMappingService.getCharMappingFile(dictId).ifPresent(file -> {
213                 RenderDataUtil.register(data, "path", file.getPath());
214             }).orElse(() -> {
215                 throwValidationError(messages -> messages.addErrorsFailedToDownloadMappingFile(GLOBAL), this::asDictIndexHtml);
216             });
217         });
218     }
219 
220     @Execute
221     @Secured({ ROLE, ROLE + VIEW })
222     public ActionResponse download(final DownloadForm form) {
223         validate(form, messages -> {}, () -> downloadpage(form.dictId));
224         verifyTokenKeep(() -> downloadpage(form.dictId));
225         return charMappingService.getCharMappingFile(form.dictId)
226                 .map(file -> asStream(new File(file.getPath()).getName()).contentTypeOctetStream().stream(out -> {
227                     file.writeOut(out);
228                 })).orElseGet(() -> {
229                     throwValidationError(messages -> messages.addErrorsFailedToDownloadMappingFile(GLOBAL),
230                             () -> downloadpage(form.dictId));
231                     return null;
232                 });
233     }
234 
235     // -----------------------------------------------------
236     //                                                Upload
237     //                                               -------
238     @Execute
239     @Secured({ ROLE })
240     public HtmlResponse uploadpage(final String dictId) {
241         saveToken();
242         return asHtml(path_AdminDictMapping_AdminDictMappingUploadJsp).useForm(UploadForm.class, op -> {
243             op.setup(form -> {
244                 form.dictId = dictId;
245             });
246         }).renderWith(data -> {
247             charMappingService.getCharMappingFile(dictId).ifPresent(file -> {
248                 RenderDataUtil.register(data, "path", file.getPath());
249             }).orElse(() -> {
250                 throwValidationError(messages -> messages.addErrorsFailedToDownloadMappingFile(GLOBAL), this::asDictIndexHtml);
251             });
252         });
253     }
254 
255     @Execute
256     @Secured({ ROLE })
257     public HtmlResponse upload(final UploadForm form) {
258         validate(form, messages -> {}, () -> uploadpage(form.dictId));
259         verifyToken(() -> uploadpage(form.dictId));
260         return charMappingService.getCharMappingFile(form.dictId).map(file -> {
261             try (InputStream inputStream = form.charMappingFile.getInputStream()) {
262                 file.update(inputStream);
263             } catch (final IOException e) {
264                 throwValidationError(messages -> messages.addErrorsFailedToUploadMappingFile(GLOBAL),
265                         () -> redirectWith(getClass(), moreUrl("uploadpage/" + form.dictId)));
266             }
267             saveInfo(messages -> messages.addSuccessUploadMappingFile(GLOBAL));
268             return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
269         }).orElseGet(() -> {
270             throwValidationError(messages -> messages.addErrorsFailedToUploadMappingFile(GLOBAL), () -> uploadpage(form.dictId));
271             return null;
272         });
273 
274     }
275 
276     // -----------------------------------------------------
277     //                                         Actually Crud
278     //                                         -------------
279     @Execute
280     @Secured({ ROLE })
281     public HtmlResponse create(final CreateForm form) {
282         verifyCrudMode(form.crudMode, CrudMode.CREATE, form.dictId);
283         validate(form, messages -> {}, this::asEditHtml);
284         verifyToken(this::asEditHtml);
285         createCharMappingItem(form, this::asEditHtml).ifPresent(entity -> {
286             try {
287                 charMappingService.store(form.dictId, entity);
288                 saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL));
289             } catch (final Exception e) {
290                 throwValidationError(messages -> messages.addErrorsCrudFailedToCreateCrudTable(GLOBAL, buildThrowableMessage(e)),
291                         this::asEditHtml);
292             }
293         }).orElse(() -> {
294             throwValidationError(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL), this::asEditHtml);
295         });
296         return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
297     }
298 
299     @Execute
300     @Secured({ ROLE })
301     public HtmlResponse update(final EditForm form) {
302         verifyCrudMode(form.crudMode, CrudMode.EDIT, form.dictId);
303         validate(form, messages -> {}, this::asEditHtml);
304         verifyToken(this::asEditHtml);
305         createCharMappingItem(form, this::asEditHtml).ifPresent(entity -> {
306             try {
307                 charMappingService.store(form.dictId, entity);
308                 saveInfo(messages -> messages.addSuccessCrudUpdateCrudTable(GLOBAL));
309             } catch (final Exception e) {
310                 throwValidationError(messages -> messages.addErrorsCrudFailedToUpdateCrudTable(GLOBAL, buildThrowableMessage(e)),
311                         this::asEditHtml);
312             }
313         }).orElse(() -> {
314             throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.getDisplayId()), this::asEditHtml);
315         });
316         return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
317     }
318 
319     @Execute
320     @Secured({ ROLE })
321     public HtmlResponse delete(final EditForm form) {
322         verifyCrudMode(form.crudMode, CrudMode.DETAILS, form.dictId);
323         validate(form, messages -> {}, this::asDetailsHtml);
324         verifyToken(this::asDetailsHtml);
325         charMappingService.getCharMappingItem(form.dictId, form.id).ifPresent(entity -> {
326             try {
327                 charMappingService.delete(form.dictId, entity);
328                 saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
329             } catch (final Exception e) {
330                 throwValidationError(messages -> messages.addErrorsCrudFailedToDeleteCrudTable(GLOBAL, buildThrowableMessage(e)),
331                         this::asEditHtml);
332             }
333         }).orElse(() -> {
334             throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.getDisplayId()), this::asDetailsHtml);
335         });
336         return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
337     }
338 
339     //===================================================================================
340     //                                                                        Assist Logic
341     //                                                                        ============
342 
343     private static OptionalEntity<CharMappingItem> getEntity(final CreateForm form) {
344         switch (form.crudMode) {
345         case CrudMode.CREATE:
346             final CharMappingItem entity = new CharMappingItem(0, StringUtil.EMPTY_STRINGS, StringUtil.EMPTY);
347             return OptionalEntity.of(entity);
348         case CrudMode.EDIT:
349             if (form instanceof EditForm) {
350                 return ComponentUtil.getComponent(CharMappingService.class).getCharMappingItem(form.dictId, ((EditForm) form).id);
351             }
352             break;
353         default:
354             break;
355         }
356         return OptionalEntity.empty();
357     }
358 
359     protected OptionalEntity<CharMappingItem> createCharMappingItem(final CreateForm form, final VaErrorHook hook) {
360         try {
361             return createCharMappingItem(this, form, hook);
362         } catch (final ValidationErrorException e) {
363             saveToken();
364             throw e;
365         }
366     }
367 
368     public static OptionalEntity<CharMappingItem> createCharMappingItem(final FessBaseAction action, final CreateForm form,
369             final VaErrorHook hook) {
370         return getEntity(form).map(entity -> {
371             final String[] newInputs = splitLine(form.inputs);
372             validateMappingString(action, newInputs, "inputs", hook);
373             entity.setNewInputs(newInputs);
374             final String newOutput = form.output;
375             entity.setNewOutput(newOutput);
376             return entity;
377         });
378     }
379 
380     // ===================================================================================
381     //                                                                        Small Helper
382     //                                                                        ============
383     protected void verifyCrudMode(final int crudMode, final int expectedMode, final String dictId) {
384         if (crudMode != expectedMode) {
385             throwValidationError(messages -> {
386                 messages.addErrorsCrudInvalidMode(GLOBAL, String.valueOf(expectedMode), String.valueOf(crudMode));
387             }, () -> asListHtml(dictId));
388         }
389     }
390 
391     private static void validateMappingString(final FessBaseAction action, final String[] values, final String propertyName,
392             final VaErrorHook hook) {
393         if (values.length == 0) {
394             return;
395         }
396         for (final String value : values) {
397             if (value.indexOf(',') >= 0) {
398                 action.throwValidationError(messages -> {
399                     messages.addErrorsInvalidStrIsIncluded(propertyName, value, ",");
400                 }, hook);
401             }
402             if (value.indexOf("=>") >= 0) {
403                 action.throwValidationError(messages -> {
404                     messages.addErrorsInvalidStrIsIncluded(propertyName, value, "=>");
405                 }, hook);
406             }
407         }
408     }
409 
410     private static String[] splitLine(final String value) {
411         if (StringUtil.isBlank(value)) {
412             return StringUtil.EMPTY_STRINGS;
413         }
414         final String[] values = value.split("[\r\n]");
415         final List<String> list = new ArrayList<>(values.length);
416         for (final String line : values) {
417             if (StringUtil.isNotBlank(line)) {
418                 list.add(line.trim());
419             }
420         }
421         return list.toArray(new String[list.size()]);
422     }
423 
424     // ===================================================================================
425     //                                                                              JSP
426     //                                                                           =========
427 
428     protected HtmlResponse asDictIndexHtml() {
429         return redirect(AdminDictAction.class);
430     }
431 
432     private HtmlResponse asListHtml(final String dictId) {
433         return asHtml(path_AdminDictMapping_AdminDictMappingJsp).renderWith(data -> {
434             RenderDataUtil.register(data, "charMappingItemItems", charMappingService.getCharMappingList(dictId, charMappingPager));
435         }).useForm(SearchForm.class, setup -> {
436             setup.setup(form -> {
437                 copyBeanToBean(charMappingPager, form, op -> op.include("id"));
438             });
439         });
440     }
441 
442     private HtmlResponse asEditHtml() {
443         return asHtml(path_AdminDictMapping_AdminDictMappingEditJsp);
444     }
445 
446     private HtmlResponse asDetailsHtml() {
447         return asHtml(path_AdminDictMapping_AdminDictMappingDetailsJsp);
448     }
449 
450 }